(function($) {

	var StopException = function(message) {
		this.message = message;

	};
	StopException.prototype = {
		toString : function() {
			return this.message;
		}
	};
	window.StopException = StopException;

	var Executer = function(instance, callback) {
		this.callback = callback;
		this.instance = instance;
		return this;
	};
	Executer.prototype = {
		/**
		 * 执行一个命令组
		 *
		 * @param cmdId
		 *            {string} 命令组ID
		 * @param cmdList
		 *            {array} 命令组
		 * @param id
		 *            {string}
		 * @param impVar
		 *            {object} 入参对象
		 * @param runtime
		 *            {object} 运行命令组的环境
		 */
		doCmdList : function(cmdId, cmdList, id, impVar, runtime) {
			if (!cmdList || cmdList.length == 0)
				return impVar;

			impVar.RUNTIME = runtime;
			impVar.INSTANCE = runtime.instance;
			var sandbox = new SandBox(this, impVar, runtime);
			doCmdList(sandbox, cmdId, cmdList);
			sandbox._completed = true;

			if ($.isFunction(this.callback)) {
				if (Object.keys(sandbox.$bindProps).length > 0) {
					this.callback(sandbox.$bindProps, runtime);
					sandbox.$bindProps = {};
				}
			}
			return impVar;
		},

		/**
		 * 只在流程判断时用到，组件无流程，所以组件对应的信息直接默认了
		 *
		 * @param express
		 * @param impVar
		 * @param runtime
		 * @returns
		 */
		evalCmd : function(express, impVar, runtime) {
			var sandbox = new SandBox(this, impVar, runtime);
			express = this.doScript(sandbox, express, impVar, 'Cmd2 - evalCmd-');
			return express;
		}
	};
	window.Executer = Executer;

	var SandBox = function(executer, impVar, runtime) {
		this.R = $.extend(impVar, runtime.data.pub, runtime.data.local);
		this.$E = {};
		this.$D = {};
		this.$Z = {};
		this.$url = window.$E.url;
		this.$uid = window.$E.uid;
		this.$bindProps = {};
		this.$dynId = runtime.struct.id; 		// 动态页面主结构ID
		this.$dynGuid = runtime.struct.guid;	// 动态页面主结构GUID
		this.$cmds = runtime.struct.cmds; 		// 动态页面命令集合
		this.$struct = runtime.struct;
		this.$runtime = runtime; 				// 当前运行环境
		this.$executer = executer;				// 当前执行者
		this.$instance = executer.instance; 	// 当前实例
		this.$parameters = this.$instance.parameters;	// 当前实例参数
		this.$DSL = this.$instance.DSL;			// SQL片段缓存
		this.$objective = this.$instance.objective;
		this.$project = this.$instance.project; // 当前项目
		this.$token = this.$project.token; 		// 当前用户凭据
		this.$digest = this.$project.digest;
		this.$softphone = window.$E.softphone; 	// 软电话
		this.$staff = window.$E.staff;			// 当前账号对象

		this.Event = new $PublicFun.$LIB.EVENT(this);
		this.Roll = new $PublicFun.$LIB.ROLL(this);
		this.Grid = new $PublicFun.$LIB.GRID(this);
		this.Security = new $PublicFun.$LIB.SECURITY(this);
		this.Property = new $PublicFun.$LIB.PROPERTY(this);
		this.Codetable = new $PublicFun.$LIB.CODETABLE(this);
		this.Perz = new $PublicFun.$LIB.PERZ(this);
		this.Step = new $PublicFun.$LIB.STEP(this);
		this.Utils = new $PublicFun.$LIB.UTILS(this);
		this.Bound = new $PublicFun.$LIB.BOUND(this);
		this.Bound2 = new $PublicFun.$LIB.BOUND2(this);
		this.Xml = new $PublicFun.$LIB.XML(this);
		this.Merge = new $PublicFun.$MERGE(this);
		this.SMS = new $PublicFun.$LIB.SMS(this);
		this.Pinyin = new $PublicFun.$LIB.PINYIN(this);

		if ($PublicFun.$ADVLIB) {
			this.Jedis = new $PublicFun.$ADVLIB.JEDIS(this);
			this.File = new $PublicFun.$ADVLIB.FILE(this);
			this.Commons = new $PublicFun.$ADVLIB.COMMONS(this);
			this.WO3 = new $PublicFun.$ADVLIB.WO3(this);
			this.Workorder3 = $PublicFun.$ADVLIB.WO3;
			this.Shorten = new $PublicFun.$ADVLIB.SHORTEN(this);
			this.Perfers = new $PublicFun.$ADVLIB.PERFERS(this);
			this.Module = new $PublicFun.$ADVLIB.MODULE(this);
			this.Rule = new $PublicFun.$ADVLIB.RULE(this);
		}

		// 该方法建议有Event类替代
		this.Callback = {
			sandbox : this,
			register : function(name, callback) {
				var sandbox = this.sandbox;
				if ($.isFunction(callback))
					sandbox.setInstanceItem('_callback_' + name, callback);
				else {
					if (!callback)
						return $F.err('Callback - register', '回调命令未定义');
					sandbox.setInstanceItem('_callback_' + name, function(v) {
						sandbox.$instance.doCmd(callback, {
							RESULT : v
						}, sandbox.$runtime)
					});
				}
			},

			fire : function(name, ev) {
				var callback = this.sandbox.getInstanceItem('_callback_' + name);
				if (callback == undefined)
					$F.err('Callback - fire', '未注册的回调函数: ' + name);
				else {
					if ($.isFunction(callback))
						callback(ev);
					else
						$F.err('Callback - fire', '无效的回调函数', callback)
				}
			}
		}
	};

	SandBox.prototype = {
		evalCmd : function(express) {
			try {
				// const AsyncFunction = Object.getPrototypeOf(function () {}).constructor
				// const asyncFunc = new AsyncFunction('with(this){\n' + express + '\n}')
				// return asyncFunc.call(this)

				return eval('with(this){\n' + express + '\n}')
			} catch (e) {
				if (e instanceof StopException) {
					return 'STOP-RUN-NEXT-COMMAND';
				} else {
					$F.err('SandBox - evalCmd', e);
				}
			}
		},

		/**
		 * 记录一条日志
		 *
		 * @param e
		 *            {string} 日志消息
		 * @param obj
		 *            {any} 在制台上进行的对象输出
		 */
		log : function() {
			var args = [ 'Cmd2 - log' ].concat(Array.prototype.splice.call(arguments, 0))
			$F.log.apply(this, args);
		},

		/**
		 * 在控制台打印一条日志记录
		 *
		 * @param e
		 *            {string} 日志消息
		 * @param obj
		 *            {any} 在控制台上进行的对象输出
		 */
		out : function() {
			if (! $E.debug)
				return;

			var args = [ '%c[DEBUG]', 'color:#FF0000' ].concat(Array.prototype.splice.call(arguments, 0));
			console.log.apply(console, args)
		},

		/**
		 * 弹出一个消息框，使用蓝色提示图标
		 *
		 * @param e {string} 消息内容
		 * @param callback {string|function} 回调命令组ID或者回调方法
		 * @param [state] integer 类型, 0=msg, 1=info, 2=warn, 3=alert
		 * @param [htmlallow] integer 是否允许使用html源代码
		 */
		msg : function(e, callback, state, htmlallow) {
			var runtime = this.$runtime;
			if ($.isFunction(callback)) {
				$F.msg(e, callback, state, htmlallow);
			} else {
				if (callback) {
					$F.msg(e, function() {
						runtime.instance.doCmd(callback, {}, runtime);
					}, state, htmlallow)
				} else
					$F.msg(e, false, state, htmlallow)
			}
		},

		/**
		 * 弹出一个提示框，使用绿色提示图标
		 *
		 * @param e {string} 消息内容
		 * @param callback {string|function} 回调命令组ID或者回调方法
		 */
		info : function(e, callback) {
			this.msg(e, callback, 1);
		},

		/**
		 * 弹出一个警示框，使用橙色提示图标
		 *
		 * @param e {string} 消息内容
		 * @param callback {string|function} 回调命令组ID或者回调方法
		 */
		warn : function(e, callback) {
			this.msg(e, callback, 2);
		},

		/**
		 * 弹出一个错误框，使用红色提示图标
		 *
		 * @param e {string} 消息内容
		 * @param callback {string|function} 回调命令组ID或者回调方法
		 */
		alert : function(e, callback) {
			this.msg(e, callback, 3);
		},

		/**
		 * 弹出一个询问框
		 *
		 * @param e
		 *            {string} 询问内容
		 * @param [submit] {callback | string} 确定回调方法或者命令号
		 * @param [cancel] {callback | string} 取消回调方法或者命令号
		 * @param [options] {object} 入參
		 * 	options = {
		 * 		okBtn: '确定按钮文字',
		 * 		cancelBtn: '取消按钮文字'
		 * 		zIndex: 10014
		 * 	}
		 */
		confirm : function(e, submit, cancel, options) {
			var a = this, R = a.R, submitCallback = submit, cancelCallback = cancel;
			if (!$.isFunction(submitCallback)) {
				submitCallback = function() {
					if (submit) {
						R.RESULT = 1;
						a.doCommand(submit);
					}
				}
			}
			if (!$.isFunction(cancelCallback)) {
				cancelCallback = function() {
					if (cancel) {
						R.RESULT = -1;
						a.doCommand(cancel);
					}
				}
			}

			$F.confirm(e, submitCallback, cancelCallback, options);
		},

		/**
		 * 创建一个36位的GUID
		 *
		 * @return {string}
		 */
		guid : function() {
			return Guid.raw();
		},
		newguid : function() {
			return this.guid();
		},

		/**
		 * 创建一个6位的伪ID
		 *
		 * @param [prefix]
		 *            {string} 伪ID前缀（占位合并，不得多于2位）
		 * @return {string}
		 */
		id6 : function(prefix) {
			if (prefix) {
				if (prefix.length > 2)
					prefix = prefix.substring(0, 2);
				return prefix + window.$F.randomChar(6 - prefix.length);
			}
			return $F.randomChar(6);
		},

		/**
		 * @param options
		 * 	options.dbPool {string} 连接池
		 * 	options.idKey {string} 主键
		 * 	options.visKey {string} 显示键
		 * 	options.showFlag {number} 显示格式
		 * 	options.beginId {number} 开始值
		 * 	options.addFlag {number} 步长
		 * 	options.len {number} 长度
		 * @return {*}
		 */
		genId: function(options) {
			let result;
			Ajax.post({
				url : $E.url + '/commons/genId',
				contentType : 'application/json;charset=UTF-8',
				headers : {
					token: $E.getActiveProject().token
				},
				data : JSON.stringify(options),
				async: false,
				success : function(data) {
					result = data
				}
			})
			return result;
		},

		genSnowflake: function() {
			let result;
			Ajax.post({
				url : $E.url + '/commons/genSnowflake',
				contentType : 'application/json;charset=UTF-8',
				headers : {
					token: $E.getActiveProject().token
				},
				async: false,
				success : function(data) {
					result = data
				}
			})
			return result;
		},

		/**
		 * 获取当前数据库时间
		 * @return {string}
		 */
		now : function() {
			return $F.getDbTime();
		},
		/**
		 * 获取服务器时间年份
		 */
		getYear : function() {
			var date = $F.parseDate(this.now());
			return date.getFullYear();
		},
		/**
		 * 获取服务器时间月份
		 */
		getMonth : function() {
			var date = $F.parseDate(this.now());
			return date.getMonth() + 1;
		},
		/**
		 * 获取服务器时间月历日
		 */
		getDate : function() {
			var date = $F.parseDate(this.now());
			return date.getDate();
		},
		/**
		 * 获取服务器时间星期
		 */
		getDay : function() {
			var date = $F.parseDate(this.now());
			return date.getDay() + 1;
		},
		/**
		 * 获取服务器时间小时
		 */
		getHours : function() {
			var date = $F.parseDate(this.now());
			return date.getHours();
		},
		/**
		 * 获取服务器时间分钟
		 */
		getMinutes : function() {
			var date = $F.parseDate(this.now());
			return date.getMinutes();
		},
		/**
		 * 获取服务器时间秒
		 */
		getSeconds : function() {
			var date = $F.parseDate(this.now());
			return date.getSeconds();
		},
		/**
		 * 获取服务器时间毫秒
		 */
		getMilliseconds : function() {
			var date = $F.parseDate(this.now());
			return date.getMilliseconds();
		},
		/**
		 * 获取服务器时间戳
		 */
		getTime : function() {
			var date = $F.parseDate(this.now());
			return date.getTime();
		},
		/**
		 * 比较两个日期时间（忽略毫秒）
		 */
		dateDiff : function(d1, d2) {
			var a1 = $F.parseDate(d1), a2 = $F.parseDate(d2);
			return (a2.getTime() - a1.getTime()) / 1000;
		},
		datediff : function(d1, d2) {
			return this.dateDiff(d1, d2);
		},
		/**
		 * 在日期时间上增加值
		 * @param unit {string}	增加值的类型，包括： year=年份；month=月份；day=月历日；hour=小时；minute=分钟；second=秒
		 * @param date {string} 日期时间的字符串
		 * @param number {integer} 增加值
		 * @param {string} 增加值后的日期时间
		 */
		dateAdd : function(unit, d, len) {
			try {
				var inDate = $F.parseDate(d);
				var inInt = inDate.valueOf();
				unit = unit.toLowerCase();
				if (unit == "year") {
					var y1 = inDate.getFullYear() + len;
					var y2 = inDate.getMonth() + 1;
					var y3 = inDate.getDate();
					if (d.trim().length > 11) {
						var y4 = inDate.getHours();
						var y5 = inDate.getMinutes();
						var y6 = inDate.getSeconds();
						return y1 + '-' + (y2.toString().length > 1 ? y2 : "0" + y2) + '-' + (y3.toString().length > 1 ? y3 : "0" + y3) + ' ' + (y4.toString().length > 1 ? y4 : "0" + y4) + ':' + (y5.toString().length > 1 ? y5 : "0" + y5) + ':' + (y6.toString().length > 1 ? y6 : "0" + y6);
					} else {
						return y1 + '-' + (y2.toString().length > 1 ? y2 : "0" + y2) + '-' + (y3.toString().length > 1 ? y3 : "0" + y3);
					}

				} else if (unit == "month") {
					var y1 = inDate.getFullYear();
					var y2 = inDate.getMonth() + 1;
					if (len > 0) {
						for (var i = 0; i < len; i++) {
							y2++;
							if (y2 > 12) {
								y1++;
								y2 = 1;
							}
						}
					} else {
						for (var i = 0; i < -len; i++) {
							y2--;
							if (y2 < 1) {
								y1--;
								y2 = 12;
							}
						}
					}

					var y3 = inDate.getDate();
					if (d.trim().length > 11) {
						var y4 = inDate.getHours();
						var y5 = inDate.getMinutes();
						var y6 = inDate.getSeconds();
						return y1 + '-' + (y2.toString().length > 1 ? y2 : "0" + y2) + '-' + (y3.toString().length > 1 ? y3 : "0" + y3) + ' ' + (y4.toString().length > 1 ? y4 : "0" + y4) + ':' + (y5.toString().length > 1 ? y5 : "0" + y5) + ':' + (y6.toString().length > 1 ? y6 : "0" + y6);
					} else {
						return y1 + '-' + (y2.toString().length > 1 ? y2 : "0" + y2) + '-' + (y3.toString().length > 1 ? y3 : "0" + y3);
					}
				} else if (unit == "day") {
					inInt += len * (24 * 60 * 60 * 1000);
					inDate = new Date(inInt);
					if ($.type(d) !== 'string' || d.trim().length > 11) {
						return inDate.format();
					} else {
						return inDate.format();
					}
				} else if (unit == "hour") {
					inInt += len * (60 * 60 * 1000);
				} else if (unit == "minute") {
					inInt += len * (60 * 1000);
				} else if (unit == "second") {
					inInt += len * 1000;
				} else {
					$PublicFun.err("Cmd2 - dateAdd", "错误的时间单位:" + unit);
					return d;
				}
				inDate = new Date(inInt);
				return inDate.format();
			} catch (e) {
				$F.err("Cmd2 - dateAdd", '日期操作失败', e);
				return d;
			}
		},
		dateadd : function(unit, d, len) {
			return this.dateAdd(unit, d, len);
		},

		/**
		 * 时间比较，date1大于date2时返回1，小于返回-1，等于返回0
		 * @param date1 {date|int|string}
		 * @param date2 {date|int|string}
		 * @returns {Number} {1|0|-1}
		 */
		dateCompare: function(date1, date2) {
			var d1 = $F.parseDate(date1), d2 = $F.parseDate(date2);
			if (!d1 && !d2) {
				return 0;
			}else if (!d1) {
				return -1;
			} else if (!d2){
				return 1;
			} else {
				if (d1.getTime() > d2.getTime()) {
					return 1;
				} else if (d1.getTime() < d2.getTime()) {
					return -1;
				} else {
					return 0;
				}
			}
		},
		datecompare: function(date1, date2) {
			return this.dateCompare(date1, date2);
		},

		/**
		 * 获取当前数据库类型
		 * @param dbPool
		 * @return {string}
		 */
		dbType : function(dbPool) {
			return this.$project.getDbType(dbPool);
		},
		dbtype : function(dbPool) {
			return this.dbType(dbPool);
		},

		/**
		 * 执行命令组
		 * @param cmdId {string | integer} 命令号
		 * @param ev {object} 入參对象
		 */
		cmd: function(cmdId, ev) {
			//this.$instance.doCmd(cmdId, $.extend(this.R, ev), this.$runtime);
			this.$instance.doCmd(cmdId, $.extend(this.R, ev), this.$runtime);
		},
		doCommand : function(cmdId) {
			this.cmd(cmdId);
		},
		asyncCmd: function(cmdId, ev, wait) {
			var $instance = this.$instance, R = this.R, $runtime = this.$runtime;
			$F.process(function() {
				$instance.doCmd(cmdId, $.extend(R, ev), $runtime);
			}, '', false, wait || 0)
		},


		/**
		 * 对一个数组变量进行轮循命令操作
		 * @method circleCmd
		 * @param property {string | array} 数组变量名或者数组对象
		 * @param cmdId {string} 执行轮循的命令ID
		 */
		circleCmd: function(property, cmdId) {
			var a = this;
			if ($.type(property) == 'string') {
				property = getVar(a, property);
			}
			if ($.type(property) == 'array') {
				$.each(property, function(i, v) {
					a.$instance.doCmd(cmdId, $.extend(true, a.R, {
						EVENT: {
							CIRCLEROW: i + 1,
							CIRCLEDATA: v
						}
					}), a.$runtime);
				})
			} else {
				$F.err('SandBox - circleCmd', '使用了非数组变量操作轮询命令', property)
			}
		},

		pcmd: function(cmdId, ev, callback) {
			doGlobalCmd(this, {
				cmdId: cmdId
			}, 'Cmd2 - pcmd');
		},

		/**
		 * 调用服务器命令
		 * @param cmdId {string} 服务器命令guid或者key
		 * @param params {object} 入參数据对象。服务器端获取入参统一为CMD_IN, 出参统一为CMD_OUT
		 * @param [callback] {function | string} 回调方法或者命令id
		 * @example
		 * 	scmd('test', {
		 * 		P1: R.var.t1,	// 服务器获取参数为：R.CMD_IN.P1
		 * 		P2: R.var.t2	// 服务器获取参数为：R.CMD_IN.P2
		 *  }, 'callbackCmdId')	// 服务器将回传的数据赋值于R.CMD_OUT中，执行返回Result的value即可获取
		 *
		 */
		scmd: function(cmdId, params, callback) {
			if (callback)
				doServerCmdImpl(this, cmdId, params, callback);
			else
				return doServerCmdImpl(this, cmdId, params);
		},

		/**
		 * 获取当前运行环境下的某个NGS控件
		 * @return {object} NGS控件对象
		 */
		ctl : function(ctlId) {
			return this.$runtime.ctls[ctlId];
		},
		getCtl : function(ctlId) {
			return this.ctl(ctlId);
		},

		/**
		 * 触发一个控件的事件
		 *
		 * @param cti {string | object} 控件ID或对象
		 * @param e {string} 事件名称
		 */
		fireCtl : function(ctl, e) {
			CtlUtils.fireCtl(this.$runtime, ctl, e || 'change');
		},

		get : function(e) {
			return e;
		},

		json : function(e) {
			return e;
		},

		/**
		 * 为变量赋值或者从变量取值
		 * @param propertyName {string} 变量名
		 * @param [value] {any} 赋值。可选，为定义时代表取值
		 * @return {any}
		 */
		val : function(propertyName) {
			propertyName = propertyName.toLowerCase();
			if (arguments.length < 2)
				return getVar(this, propertyName);
			else
				setVar(this, propertyName, arguments[1], arguments.length > 2 ? arguments[2] : false);
		},
		setValue: function(propertyName, value) {
			this.val(propertyName, value);
		},
		setVar: function(fn, propertyName, value, quite) {
			setVar(this, propertyName, value, quite);
		},
		getValue: function(propertyName) {
			return this.val(propertyName);
		},

		iif : function(e, t, f) {
			return e? t:f;
		},

		/**
		 * 比较两个值，如果是字符串值则自动去除空格
		 * @param a {any} 值a
		 * @param b {any} 值b
		 * @param [force] {boolean} 是否使用强制匹配，默认为false
		 */
		equal : function(a, b, force) {
			if (a instanceof String)
				a = a.trim();
			if (b instanceof String)
				b = b.trim();
			if (force)
				return a === b;
			else
				return a == b;
		},

		/**
		 * 给控件绑定一个显隐状态变量
		 * @method bindState
		 * @example:
		 * 			bindState('var.xxx', 6) 绑定var.xxx到当前控件显隐状态，显示值为6
		 * 		or
		 * 			bindState('c2', 'var.xxx', 6) 绑定var.xxx到c2控件显隐状态，显示值为6
		 * 		or
		 * 			bindState('var.xxx', function(data){
		 * 				if (data > 5)
		 * 					return true;
		 * 				else
		 * 					return false;
		 * 			})
		 * @param ctl {string | object} 控件对象或者控件ID
		 * @param propertyName {string} 变量名
		 * @param value {any | function} 显示状态匹配值，当变量值和value相等时，该控件显示，否则隐藏。value可为具体值或者回调方法
		 * @return {boolean}
		 */
		bindState : function(ctl, propertyName, value) {
			if (arguments.length < 3) {
				value = propertyName;
				propertyName = ctl;
				ctl = this.R.CTRL;
			} else {
				if ($.type(ctl) == 'string')
					ctl = this.getCtl(ctl);
			}
			if (ctl) {
				ctl.state(this.$instance.getValue(propertyName, this.$runtime) == value ? 0 : 1);
				return this.$instance.bindValue(propertyName, ctl, function(e){
					if ($.isFunction(value))
						ctl.state(value(e.data) ? 0 : 1);
					else
						ctl.state(e.data == value ? 0 : 1);
				});
			} else
				return false;
		},

		/**
		 * 获得一个随机数
		 * @param [min] {integer} 可选。最小值，默认是0
		 * @param [max] {integer} 可选。最大值
		 * @example:
		 * 		random();		// 取0到1之间的一个随机高精度数值
		 * 		random(9);		// 取0到9之间的某个随机整数
		 * 		random(10,30)	// 取10到30之间的某个随机整数
		 * @return {integer} 随机数
		 */
		random : function() {
			var min = 0, max, range;
			if (arguments.length > 1) {
				min = arguments[0];
				max = arguments[1];
			} else if (arguments.length == 1)
				max = arguments[0];
			else
				max = 0;
			range = max - min;

			if (range == 0)
				return Math.random();
			else
				return Math.floor(Math.random() * ( range + 1)) + min;
		},

		/**
		 * 在字符串中查询关键词，并从第一个符合查询结果的起始位开始向后截取
		 * @example:
		 * 		R.var.string = 'i am test for string';
		 * 		substrmid(R.var.string, 'test');	// return: 'test for string'
		 * @param string {string}	原始字符串
		 * @param find	{string}	进行查询的关键词
		 * @return {string} 截取后的字符串
		 */
		substrmid : function(string, find) {
			var index = string.indexOf(find);
			if (index >= 0) {
				return string.substring(index);
			} else {
				return '';
			}
		},

		/**
		 * 在字符串中查询关键词，并从第一个符合查询结果的起始位开始向前截取
		 * @example:
		 * 		R.var.string = 'i am test for string';
		 * 		substrleft(R.var.string, 'test');	// return: 'i am '
		 * @param string {string}	原始字符串
		 * @param find	{string}	进行查询的关键词
		 * @return {string} 截取后的字符串
		 */
		substrleft : function(string, find) {
			var index = string.indexOf(find);
			if (index >= 0) {
				return string.substring(0, index);
			} else {
				return '';
			}
		},

		/**
		 * 在字符串中查询关键词，并从第一个符合查询结果的起始位开始向后截取（不包含查询关键词本身）
		 * @example:
		 * 		R.var.string = 'i am test for string';
		 * 		substrright(R.var.string, 'test');	// return: ' for string'
		 * @param string {string}	原始字符串
		 * @param find	{string}	进行查询的关键词
		 * @return {string} 截取后的字符串
		 */
		substrright : function(string, find) {
			var index = string.indexOf(find);
			if (index >= 0) {
				return string.substring(index + find.length);
			} else {
				return '';
			}
		},

		/**
		 * 通知系统即可刷新变量到UI
		 * @param [propertyName] {string} 变量名。可选，为空时刷新全部已经修改的变量
		 * @param [data] {any} 变量值。可选，不为空时将动态赋值给当前指定变量
		 */
		refresh : function(propertyName, data) {
			if (propertyName) {
				propertyName = propertyName.toLowerCase();
				var cd = {};
				if (this.$bindProps[propertyName]) {
					cd[propertyName] = (data ? data : this.$bindProps[propertyName]);
					delete this.$bindProps[propertyName];
				} else {
					cd[propertyName] = (data ? data : {
						type: 0,
						data: getVar(this, propertyName)
					});
				}
				this.$executer.callback(cd, this.$runtime);
			} else {
				this.$executer.callback(this.$bindProps, this.$runtime);
				this.$bindProps = {};
			}
		},

		/**
		 * 清除DSL片段
		 * @param [dslKey] {string} 被删除的DSL片段KEY，默认为空，删除全部DSL片段
		 * @param [dslNum] {integer} 被删除的DSL片段KEY的序列，默认为空，删除指定DSL片段的所有序列
		 */
		clearDsl : function(dslKey, dslNum) {
			if (! dslKey) {
				for(var key in this.$DSL)
					delete this.$DSL[key];
			} else {
				dslKey = dslKey.toUpperCase();
				if (! dslNum)
					delete this.$DSL[dslKey];
				else {
					if (this.$DSL[dslKey])
						delete this.$DSL[dslKey][dslNum]
				}
			}
		},

		showDsl: function(dslKey) {
			if (dslKey && $E.debug)
				console.log('DSLKey: ' + dslKey, this.$DSL[dslKey]);
		},

		/**
		 * 对数组变量进行交叉
		 * @param propertyName {string} 变量名
		 * @param fixCols {array} 固定列号，下标为1。例如[1,2,3]
		 * @param transCol {integer} 交叉列号， 下标为1
		 * @param dataCol {integer} 数据列号，下标为1
		 * @return {array} 交叉完成后的数组
		 */
		transArray : function(propertyName, fixCols, transCol, dataCol) {
			var property = this.$instance.getProperty(propertyName, this.$runtime);
			if (property) {
				if (property.t !== 4) {
					$F.err("Cmds - transArray", "数据源变量必须是数组类型");
					return [];
				}
				var sourceData = this.$instance.getValue(propertyName.toLowerCase());
				return $PublicFun.toTransArray(sourceData, fixCols, transCol, dataCol);
			} else {
				$F.err("Cmds - transArray", "数据源对象不存在");
				return [];
			}
		},

		/**
		 * 注册一个键盘监听事件
		 * @param key {string} 键盘监听代码
		 * @param callback {function | string} 执行监听的回调方法或者命令ID
		 */
		hotkey : function(key, cmdId) {
			var instance = this.$instance, runtime = this.$runtime, R = this.R;
			$E.registerHotkey(instance, key, function(e) {
				if ($.isFunction(cmdId))
					cmdId(R);
				else
					instance.doCmd(cmdId, {}, runtime);
			})
		},


		/**
		 * 在当前实例或者runtime中创建一个内置方法（实例内有效）
		 * @param name {string} 方法名
		 * @param callback {function} 方法实体
		 * @param runtimeScope {boolean} 是否创建与runtime环境内，默认创建与实例中
		 */
		storeMethod : function(name, callback, runtimeScope) {
			var store = runtimeScope ? this.$runtime : this.$instance;
			if (! store.methods)
				store.methods = {};
			store.methods[name] = callback;
		},

		/**
		 * 执行某个在实例已创建的方法
		 * @param name {string} 方法名
		 * @param arguments {...} 入参
		 * @return {any} 执行结果
		 */
		doMethod : function(name) {
			try {
				if (this.$instance.methods[name]) {
					var args = [].slice.call(arguments, 1);
					return this.$instance.methods[name].apply(this, args);
				}
			} catch(e) {
				$F.err('Cmd2 - doMethod', e);
			}
		},

		/**
		 * 执行某个在runtime环境中已创建的方法
		 * @param name {string} 方法名
		 * @param arguments {...} 入参
		 * @return {any} 执行结果
		 */
		doMethodRT : function(namespace, name) {
//			console.log(this.$runtime)
			var ns = this.$runtime.namespace + '.' + namespace;
			let runtime = this.findRuntime(ns);
			if (! runtime) {
				$F.err('不存在的运行时：' + ns, this.$runtime);
				return;
			}
			try {
				if (runtime.methods && runtime.methods[name]) {
					var args = [].slice.call(arguments, 1);
					return runtime.methods[name].apply(this, args);
				} else
					$F.err('Cmd2 - doMethodRT', 'Method ' + name + ' not found', runtime);
			} catch(e) {
				$F.err('Cmd2 - doMethodRT', e);
			}
		},

		findRuntime : function(namespace) {
			let runtime = this.$runtime, child;
			if (arguments.length > 1) {
				runtime = arguments[0];
				namespace = arguments[1];
			}

			if (namespace == runtime.namespace)
				return runtime;

			// console.log(runtime.namespace + ' vs ' + namespace)
			for (let key in runtime.children) {
				child = this.findRuntime(runtime.children[key], namespace);
				if (child)
					return child;
			}
		},

		/**
		 * 向实例环境写入一个数据，用于同页面内数据交互
		 * @method setInstanceItem
		 * @param name {string} 数据名
		 * @param item {any} 数据对象
		 */
		setInstanceItem : function(name, item) {
			this.$runtime.instance.setAttribute(name, item);
		},

		/**
		 * 从实例环境读取一个数据
		 * @method getInstanceItem
		 * @param name {string} 数据名
		 * @return {any} 数据对象
		 */
		getInstanceItem : function(name) {
			return this.$runtime.instance.getAttribute(name);
		},

		/**
		 * 从实例环境删除一个数据
		 * @method removeInstanceItem
		 * @param name {string} 数据名
		 */
		removeInstanceItem : function(name) {
			this.$runtime.instance.removeAttribute(name);
		},

		/**
		 * 向项目环境写入一个数据，用于跨页面数据交互
		 * @method setProjectItem
		 * @param name {string} 数据名
		 * @param item {any} 数据对象
		 */
		setProjectItem : function(name, item) {
			this.$runtime.project.setAttribute(name, item);
		},

		/**
		 * 从项目环境读取一个数据
		 * @method getProjectItem
		 * @param name {string} 数据名
		 * @return {any} 数据对象
		 */
		getProjectItem : function(name) {
			return this.$runtime.project.getAttribute(name);
		},

		/**
		 * 从项目环境删除一个数据
		 * @method removeProjectItem
		 * @param name {string} 数据名
		 */
		removeProjectItem : function(name) {
			return this.$runtime.project.removeAttribute(name);
		},

		/**
		 * 向NGS全局环境写入一个数据，用于框架核心数据交互
		 * @method setGlobalItem
		 * @param name {string} 数据名
		 * @param item {any} 数据对象
		 */
		setGlobalItem : function(name, item) {
			window.$Z = window.$Z || {};
			window.$Z[name.toLowerCase()] = item;
		},

		/**
		 * 从NGS全局环境读取一个数据
		 * @method getGlobalItem
		 * @param name {string} 数据名
		 * @return {any} 数据对象
		 */
		getGlobalItem : function(name) {
			window.$Z = window.$Z || {};
			return window.$Z[name];
		},

		/**
		 * 从NGS全局环境删除一个数据
		 * @method removeGlobalItem
		 * @param name {string} 数据名
		 */
		removeGlobalItem : function(name) {
			window.$Z = window.$Z || {};
			delete window.$Z[name];
		},

		/**
		 * 置入一个定时任务
		 *
		 * @method putTask
		 * @param name {string} 定时器名称
		 * @param delay {integer} 延时执行秒
		 * @param immediate {boolean} 是否立即执行
		 * @param cmdId {string} 任务回调的命令组ID
		 * @param [ev] {any} 任务执行入参环境, JSON格式对象
		 * @param [useWorker] {boolean} 是否使用web worker保证定时任务的正确执行
		 * @param [pauseOnInactive] {boolean} 在inactive状态下是否需要暂停
		 * @return {boolean}
		 */
		putTask : function(name, delay, immediate, cmdId, ev, useWorker, pauseOnInactive) {
			return this.$instance.putTask(this.$runtime, name, delay, immediate, cmdId, ev, useWorker, pauseOnInactive);
		},

		/**
		 * 清除一个定时器任务
		 *
		 * @method clearTask
		 * @param name
		 *            {string} 定时器名称
		 */
		clearTask : function(name) {
			return this.$runtime.instance.clearTask(name);
		},

		/**
		 * 打开一个动态页面
		 *
		 * @param dynId {string} 动态页面ID
		 * @param tabName {string} TAB标签，为空时从动态页取默认名做为标签
		 * @param [parameter] {any} 打开动态页面时的入参json对象
		 * @param [keepOpen] {boolean} 是否不允许关闭，默认为false
		 * @param [veSupport] {boolean} 当页面为第二代动态页面时，指定该值为true。默认为一代动态页面
		 */
		openDyn : function(dynId, tabName, parameter, keepOpen, veSupport) {
			return this.$project.openAddinDyn(dynId, tabName, parameter, keepOpen, 'DYN', veSupport);
		},
		openAddinDyn : function(dynId, tabName, parameter, keepOpen, DYN2) {
			return this.openDyn(dynId, tabName, parameter, keepOpen, DYN2);
		},

		closePage : function() {
			this.$instance.project.tabClose(this.$instance.id);
		},

		/**
		 * 保存当前动态页面
		 * @method savePage
		 * @param flag {boolean} 保存成功后是否关闭当前动态页面
		 * @return {boolean}
		 */
		savePage : function(flag) {
			if (this.$instance.save()) {
				if (flag)
					this.closePage();
				return true;
			} else
				return false;
		},

		saveCurDyn: function(flag) {
			return this.savePage(flag);
		},

		/**
		 * 设置当前动态页面的归属人
		 *
		 * @param owner
		 *            {string} 归属人
		 * @param inclusive
		 *            {boolean} 独占性
		 */
		setDynOwner : function(owner, inclusive) {
			var a = this.$instance;
			a.owner = owner;
			a.ownerInclusive = inclusive;
		},

		/**
		 * 获取当前实例客户的一个属性值
		 * @param field {string} 客户资料字段名
		 */
		getCustomerInfo : function(field) {
			var customer = this.instance.customer;
			if (customer)
				return customer.getValue(field);
			else {
				$F.err("Cmd2 - getCustomerInfo", "客户对象不存在于当前实例");
				return '';
			}
		},

		/**
		 * 给当前实例客户设置一个属性值
		 * @param field {string} 客户资料字段名
		 * @param value {any} 客户资料字段值
		 */
		setCustomerInfo : function(field, value) {
			var customer = this.instance.customer;
			if (customer)
				customer.setValue(field, value);
			else {
				$F.err("Cmd2 - getCustomerInfo", "客户对象不存在于当前实例");
			}
		},

		/**
		 * 判断当前实例客户是否相同与框架客户
		 * @return {boolean}
		 */
		isProjectCustomer : function() {
			return (this.$instance.customer.getValue('customer_guid') === this.$project.customer.getValue('customer_guid'))
		},

		/**
		 * 保存当前框架中的客户资料
		 * 注意，本方法并非针对当前实例中的客户进行保存，而是针对框架客户进行保存。
		 * 当且仅当实例客户与框架客户相同时，改方法即等效于保存实例客户资料
		 */
		saveCustomer : function() {
			var proj = this.$project;
			return new Promise(function(resolve, reject){
				proj.customer.save(function(customerGuid) {
					if (proj.customer.getProperty("customer_guid") != customerGuid) {
						proj.setCurrentCustomerByGuid(customerGuid);
					}
					resolve();
				}, function(){
					reject();
				}, null, true);
			});
		},

		/**
		 * 设置当前框架客户
		 * @param customerGuid {string} 客户GUID
		 */
		setCurrentCustomer : function(customerGuid) {
			this.$project.setCurrentCustomerByGuid(customerGuid);
		},

		/**
		 * 对某个数据进行加锁操作
		 * @param mainId {string} 主ID字段
		 * @param subId	{string} 次ID字段
		 * @param [propertyName] {string} 可选。变量名称，记录加锁人信息
		 * @return {integer} 加锁结果，1=正确，-1=失败, -2=已被他人加锁
		 */
		lock : function(mainId, subId, propertyName) {
			var result = $F.ajaxSync('JSGenerateServlet2', {
				token : this.$token,
				dbPool : '',
				name : 'System.Locked',
				mainId : mainId || '',
				subId : subId || ''
			}, this.$digest);
			if (result) {
				if (result.code > 0) {
					return result.code;
				} else {
					if (result.code == -2) {
						if (propertyName) {
							this.R[propertyName.toUpperCase()] = result.message;
						}
					}
					return result.code;
				}
			} else {
				$F.err("Cmd2 - lock", "加锁失败，返回异常");
				return -1;
			}
		},
		lockdata : function(mainId, subId, propertyName) {
			return this.lock(mainId, subId, propertyName);
		},

		/**
		 * 对某个数据进行解锁操作
		 * @param mainId {string} 主ID字段
		 * @param subId	{string} 次ID字段
		 * @return {integer} 解锁结果，1=正确，-1=失败
		 */
		unlock : function(mainId, subId) {
			var result = $F.ajaxSync('JSGenerateServlet2', {
				token : this.$token,
				dbPool : '',
				name : 'System.unLocked',
				mainId : mainId || '',
				subId : subId || ''
			}, this.$digest);
			return result? result.code : -1;
		},
		unlockdata : function(mainid, subId) {
			return this.unlock(mainId, subId);
		},

		/**
		 * 检查某个数据的加锁状态
		 * @param mainId {string} 主ID字段
		 * @param subId	{string} 次ID字段
		 * @param [propertyName] {string} 可选。变量名称，记录加锁人信息
		 * @return {integer} 检查结果
		 * 						0 = 未加锁;
		 * 						1 = 已加锁，加锁人为操作人本人;
		 * 						-1 = 检查失败;
		 * 						-2 = 已加锁，加锁人为propertyName变量记录的STAFF_ID
		 */
		checkLock : function(mainId, subId, propertyName) {
			var result = $F.ajaxSync('JSGenerateServlet2', {
				token : this.$token,
				dbPool : '',
				name : 'System.CheckLocked',
				mainId : mainId || '',
				subId : subId || ''
			}, this.$digest);
			if (result) {
				if (result.code >= 0) {
					return result.code;
				} else {
					if (result.code == -2) {
						if (propertyName) {
							this.R[propertyName.toUpperCase()] = result.message;
						}
					}
					return result.code;
				}
			} else {
				$F.err("Cmd2 - checkLock", "加锁检查失败，返回异常");
				return -1;
			}
		},
		checklockdata : function(mainId, subId, propertyName) {
			return this.checkLock(mainId, subId, propertyName);
		},

		/**
		 * 自动电话小结
		 * @param [customerGuid] {string} （可选）小结的客户guid
		 * @param [objectiveStatus] {string} （可选）小结的工单的状态
		 * @param [showFlag] {integer} （可选）1表示小结结束时候打开系统的小结模块页面
		 * @param [showNext] {string} （可选）这个参数表示小结结束后，打开什么页面：
		 * 				配置成$TEL表示打开系统外拨查询界面，配置成$JOBLIST表示打开系统的工作查询列表界面，或者直接配置成某个菜单的id，就可以直接打开此菜单
		 * @param [eventComments] {string} (可选) event的comments
		 * @return {integer}
		 */
		autoCallEnd : function(customerGuid, objectiveStatus, showFlag, showNext, eventComments) {
			var object = {
				showModule : showFlag || 0,
				nextShow : showNext || '',
				customerGuid : customerGuid,
				objectiveStatus: objectiveStatus,
				eventComments: eventComments
			};
			try {
				this.$project.events.notify($CONST.ModuleEvent.ECM_CALLSUMMARY, object);
			} catch (e) {
				$F.err("Cmd2 - callEnd", "CallEnd error: showFlag=" + showFlag + " showNext=" + showNext, e);
				return -1;
			}
			return 1;
		},

		/**
		 * 根据objective_guid和taskId打开工单3
		 * @param tabName {string} TAB标签
		 * @param oId {string} 市场活动guid
		 * @param taskId {string} 工单三任务guid
		 * @param parameters {object} 打开参数
		 * @param notifyId {string} 提醒id
		 * @param dbPool {string} 数据源
		 * @returns {*}
		 */
		openAddinWO3ByOId : function(tabName, oId, taskId, parameters, notifyId, dbPool) {
			return this.$project.openAddinWO3ByOId({
				addinId: 'AddinWO3',
				tabName,
				oId,
				taskId,
				parameters,
				notifyId,
				dbPool
			});
		},

		/**
		 * 根据objectivetype_id打开工单3，可能是新建工单
		 * @param tabName {string} TAB标签
		 * @param otId {string} 市场活动类型id
		 * @param parameters {string} 打开参数
		 * @param customerGuid {string} 打开的客户guid
		 * @param stepId {string} 步骤id
		 * @param dbPool {string} 数据源
		 * @returns {*}
		 */
		openAddinWO3ByOtId : function(tabName, otId, parameters, customerGuid, stepId, dbPool) {
			return this.$project.openAddinWOByOtId({
				addinId: 'AddinWO3',
				tabName,
				otId,
				parameters,
				wo3: true,
				customerGuid,
				stepId,
				dbPool
			});
		},

		/**
		 * 打开一个插件
		 * @method openPlugin
		 * @param name {string} 标签名称
		 * @param pluginId {string} 插件ID，参考sysc_addins表
		 * @param pluginParams {object} 插件入参，参考插件规范
		 * @param parameters {object} 方法入参，参考插件规范
		 * @param keepOpen {boolean} 标签是否不可关闭
		 */
		openPlugin : function(name, pluginId, pluginParams, parameters, keepOpen) {
			window.$E.openPlugin(name, this.$project, pluginId, pluginParams, parameters, keepOpen);
		},

		/**
		 * 在指定的面版容器中调用插件
		 *
		 * @param ctl {string | object} 目标控件ID或者控件对象
		 * @param addin {string} 插件所在的组件ID
		 * @param addinParam {any} 插件所在的组件参数
		 * @param parameters {any} 插件运行入参
		 * @param [callback] {function} 打开插件后的回调
		 * @param [async] {boolean} 是否是异步构建
		 */
		embPlugin : function(ctl, addin, addinParam, parameters, callback, async) {
			const a = this;
			if ($.type(ctl) === 'string')
				ctl = a.ctl(ctl);
			if (ctl) {
				$E.embPlugin(ctl, a.$project, addin, addinParam, parameters, function(plugin) {
					if (plugin) {
						a.$instance.embed.push(plugin);
						plugin.embedInstance = a.$instance;
						callback && callback(plugin)
					} else
						throw new Error('创建嵌入式插件失败：' + addin)
				}, async);
			}
		},

		/**
		 * 在指定的容器控件中打开一个工单
		 * @method embWo3
		 * @param ctl {string || object} 指定容器控件的ID或者对象
		 * @param woParams {object} 包括了：
		 * 		otId {string} 工单MAPID，如果是新建工单，需要入参工单ID
		 * 		oId {string} ObjectiveGuid, 打开一个已经存在的工单，需要入参ObjectiveGuid，此时woId可入参空
		 * 		taskId {string} 工单节点步骤ID, 打开一个已经存在的工单时有效
		 * 		[notifyId] {string} 工单提醒id
		 * 		[readonly] {boolean} 是否是只读打开，如果是只读打开就不会校验是否重复打开
		 * 		[rolegroupId] {string} 子流程id
		 * 		[forceProxy] {boolean} 是否强制代理
		 *		[tabc] {string} tabc表名
		 * @param [parameters] {object} 入参环境
		 */
		embWo3 : function(ctl, woParams, parameters) {
			if ($.type(ctl) === 'string')
				ctl = this.ctl(ctl);
			if (ctl) {
				if (!$.isFunction(ctl.getContainer))
					$F.err('Cmd2 - embWo3', '无法在非容器控件中打开工单 - ' + ctl.type);
				else {
					var instance = this.$project.embWo3(ctl.getContainer(), woParams, {
						FRAMEDATA : this.$runtime.data,
						PARAMS: parameters
					});
					if (instance && instance.workorder) {
						this.$instance.wos.push(instance.workorder);
						instance.workorder.rootInstance = this.$instance;
					}
				}
			}
		},

		/**
		 * 指定容器中打开一个动态页面
		 * @param ctl {object | string} 显示页面的容器控件对象或者是控件ID
		 * @param dynId {string} 动态页面ID
		 * @param [parameters] {object} 入参环境
		 */
		embDyn : function(ctl, dynId, parameters) {
			var a = this;
			if ($.type(ctl) === 'string')
				ctl = a.ctl(ctl);
			if (ctl) {
				if (!$.isFunction(ctl.getContainer))
					$F.err('Cmd2 - embDyn', '无法在非容器控件中打开动态页面' + ctl.type);
				else {
					var instance = a.$project.embDyn(ctl.getContainer(), dynId, parameters);
					if (instance) {
						a.$instance.embed.push(instance);
						instance.rootInstance = a.$instance;
					}
				}
			}
		},

		/**
		 * 执行一个外挂加载的插件方法
		 * @method cabinet
		 * @param jsName {string} 插件js文件名（不含后缀），文件位于js/amds目录下
		 * @param method {string} 执行插件的方法
		 * @param options {any} 方法入參
		 * @param callback {function | string} 执行完毕后的回调方法或者命令id
		 */
		cabinet : function(jsName, method, options, callback) {
			var a = this;
			try {
				require(['js/amds/' + jsName + $E.jsv + '.js'], function(amdObject) {
					var result = amdObject[method].call(a, options);
					if ($.isFunction(callback))
						callback(result);
					else if ($.type(callback) === 'string')
						a.cmd(callback, {RESULT: result});
				})
			} catch(e) {
				$F.err('Cmd - cabinet', e);
			}
		},

		/**
		 * 执行某个外挂的特效方法
		 * @param method {string} 特效方法名
		 * @param [arguments] {...} 可选，连续的入参，包括指定控件对象等
		 */
		effects : function(method) {
			if (! method)
				return $F.err('Cmd - effects', '没有指定特效的方法名'), false;

			try {
				var args = [].slice.call(arguments, 1);
				require(['js/amds/effects' + $E.jsv], function(Effects) {
					return Effects[method].apply(Effects, args);
				})
			}catch(e) {
				$F.err('Cmd - effects', e);
			}
		},

		/**
		 * 将某个浮动层绑定在目标控件上
		 * @deprecated
		 * @param ctl
		 *            {string | object} 目标控件ID或者控件对象
		 * @param layerCtl
		 *            {string} 浮动层控件ID
		 */
		driftLayer : function(ctl, layerId) {
			if (layerId == undefined) {
				layerId = ctl;
				ctl = this.R.CTRL;
			}
			if ($.type(ctl) == 'string') {
				if (this.$runtime.ctls[ctl])
					ctl = this.$runtime.ctls[ctl];
				else {
					$F.err('Cmd2 - driftLayer', '不存在的目标控件, ID：' + ctl);
					return;
				}
			}
			CtlUtils.driftLayer(this.$runtime, ctl, layerId);
		},

		/**
		 * 异步处理事务方法，系统将在屏幕中进行忙碌状态提示
		 * @method process
		 * @param cmdId {string | function} 处理事务的回调命令或者执行的方法
		 * @param [message] {string} 处理时提示的消息内容
		 * @param [async] {boolean} 执行的是否为异步处理，默认为false。如果执行的是异步
		 */
		process : function(cmdId, message, async, ev) {
			var a = this;
			if ($.isFunction(cmdId)) {
				$F.process(function(close){
					cmdId.call(a, close);
				}, message || '', async);
			} else {
				$F.process(function(callback){
					a.R.EVENT = a.R.EVENT || {};
					a.R.EVENT.callback = callback;
					a.cmd(cmdId, a.R);
				}, message || '', async)
			}
		},

		/**
		 * 加载一个amd插件，并执行回调
		 * @method require
		 * @param js {string} amd插件名称，不包含后缀名
		 * @param callback {function} 回调方法
		 */
		require : function(js, callback) {
			var $this = this;
			require(['js/amds/' + js + $E.jsv], function(o) {
				callback.call($this, o)
			})
		},

		/**
		 * 停止当前的命令组运行
		 */
		stop : function(e) {
			throw new StopException(e || 'Stop script running')
			//return 'STOP-RUN-NEXT-COMMAND'
		},

		submitVerify: function () {
			var ctls = this.$runtime.ctls
			for(var id in ctls) {
				if (! ctls[id].verify())
					return false;
			}
			return true;
		},

		/**
		 * 对图片验证码进行验证
		 * @param proeprtyName {string} 用于验证短信的变量名
		 * @return {boolean}
		 */
		verify : function(propertyName) {
			var a = this, property = a.$instance.getProperty(propertyName, a.$runtime);
			if (! property)
				return $F.err('Cmd2 - verify', '变量不存在: ' + propertyName), false;
			if (property.t !== 3)
				return $F.err('Cmd2 - verify', '变量不是字符串类型：' + propertyName), false;

			var v = false;
			Ajax.post({
				url : $E.url + '/verify_',
				data : {
					code: a.$instance.getValue(propertyName, a.$runtime, ''),
				},
				async: false,
				success : function(data) {
					if (data.code > 0)
						v = true;
				}
			})
			return v;
		},

		/**
		 * 发送短信验证码
		 * @method smsCodeSend
		 * @param ecsid {string} 短信验证的类型
		 * @param type {integer} 0=向当前客户发送短信（仅适用于NL模式），1=向当前staff发送短信，2=向指定手机发送短信
		 * @param mobile {string} 接收短信的手机号码（仅当type=2时有效）
		 * @return {object}
		 */
		smsCodeSend : function(ecsid, type, mobile) {
			var v = {
				code: -1,
				message: '请求异常'
			};
			Ajax.post({
				url : $E.url + '/smsCodeSend_',
				data : {
					uid: $E.uid,
					ecsid: ecsid,
					type: type,
					mobile: (mobile || '')
				},
				async: false,
				success : function(data) {
					v = data;
				}
			})
			return v;
		},

		/**
		 * 弹出滑动拼图验证面板
		 * @param ecsid {string} 验证事务key
		 * @param cmdId {string | function} 回调命令id或方法
		 */
		slidelock : function(ecsid, cmdId) {
			var a = this;
			SlideUnlocker.init({
				onVerify : function(pos, callback) {
					var req = {
						ds: $E.dataService.key,
						uid: $E.uid,
						ecsid: ecsid
					}
					if (pos)
						req.pos = pos;
					Ajax.post({
						url: $E.url + '/slidelock_',
						data: req,
						success: function(data) {
							if (data.code > 0) {
								callback(data);
								if ($.type(cmdId) == 'function')
									cmdId();
								else {
									a.cmd(cmdId)
								}
							} else {
								data.value = {
									bgImg : $E.url + '/fs/get?file=' + data.value.bg,
									blockImg : $E.url + '/fs/get?file=' + data.value.block
								}
								callback(data);
							}
						}
					})
				}
			});
		},

		ognl : function (expression) {
			var sandbox = this;
			return $F.ognl(expression, function(propertyName) {
				return getVar(sandbox, propertyName);
			});
		},

		/**
		 * 将数组或对象进行克隆复制
		 * @param object {object | array} 数组或者对象
		 * @return {object | array} 数组或对象的克隆体
		 */
		clone : function(object) {
			if ($.type(object) == 'array')
				return $.extend(true, [], object);
			else if ($.type(object) == 'object')
				return $.extend(true, {}, object);
			else
				return object;
		},

		/**
		 * 转ivr
		 * @param mute 是否是直接转
		 * @param number 转接号码
		 * @param sData 随路数据
		 * @param cmdId 回调命令组id
		 */
		transferToIVR : function(mute, number, sData, cmdId) {
			if (this.$softphone) {
				this.$softphone.ivr.transferToIVR(mute, number, sData, this.$runtime, cmdId);
			}
		},

		/**
		 * 运行一张报表
		 * @param rptId {string} 报表GUID
		 * @param params {object} 入参
		 * @param [async] {boolean} 是否使用异步运行
		 */
		runReport: function(rptId, params, async) {
			window.$E.openPlugin('报表', this.$project, 'reporter', (async ? 'S' : 'R') + '#' + rptId, params);
		},

		/**
		 * 运行一张新报表
		 * @param rptId {string} 报表GUID
		 * @param params {object} 入参
		 * @param [async] {boolean} 是否使用异步运行
		 */
		runReport2: function(rptId, params, async) {
			window.$E.openPlugin('报表', this.$project, 'newReporter', (async ? 'S' : 'R') + '#' + rptId, params);
		},

		/**
		 * 批量运行新报表
		 * @param rptId {string} 报表GUID
		 * @param params {object} 入参
		 * @param [async] {boolean} 是否使用异步运行
		 */
		runReport2Batch: function(params) {
			window.$E.openPlugin('报表', this.$project, 'newReporter', 'B'+ '#' + Guid.create(), params);
		},

		/**
		 * 对内容进行格式化
		 * @param content {string} 需要格式化的内容
		 * @param expression {string} 格式化表达式
		 * @return {string}
		 */
		format: function(content, expression) {
			return CtlUtils.expression(content, expression, this.$runtime);
		},

		/**
		 * 在组件内对系统外壳注册外挂菜单
		 * @param menus {array} 外挂菜单配置
		 * @example
		 * 	extraMenu([{
		 * 		type: 'radio'			// 菜单类型，包括：radio=单选,push=普通,sep=分割线
		 * 		title: '单选1',
		 * 		value: 0,				// 点击菜单时传送到对应命令中的参数，可以时任何形式，包括对象或者数字、字符串等
		 * 		prop: 'var.state',		// 单选菜单对应组件内的变量值，用以标示当前选择状态，当var.state的值与value相等时，该菜单为选中状态
		 * 		cmd: 'test'				// 点击菜单调用的命令
		 * 	}, {
		 * 		type: 'radio',
		 * 		title: '单选2',
		 * 		value: 1,
		 * 		prop: 'var.state',
		 * 		cmd: 'test'
		 * 	}, {
		 * 		type: 'sep'
		 * 	}, {
		 * 		type: 'push',			// 普通菜单
		 * 		title: '普通菜单',
		 * 		icon: 'cube',			// 菜单图标（fontawesome字体）
		 * 		value: {
		 * 			t1: 'hello world!',
		 * 			t2: 2018
		 * 		},
		 * 		cmd: 'test2'
		 * 	}])
		 */
		extraMenu : function(menus) {
			var sandbox = this, extra = this.$runtime.extra;
			if (! extra)
				return;

			var btnsbar = $('.btnsbar', extra);
			if ($('.extraMenu.shell', btnsbar).length > 0) {
				// 已在容器或外壳上注册，内部注册无效
				return;
			}

			$('.extraMenu', btnsbar).remove();
			$('<a href="javascript:;" class="extraMenu fas fa-server"/>').appendTo(btnsbar).on('click', function(e) {
				e.preventDefault();
				e.stopPropagation();
				$(document).click();

				var this$ = $(e.currentTarget), ul = $('<ul class="cpExtraMenu" />');
				$.each(menus, function(i, v) {
					if (v.type == 'sep') {
						ul.append('<li class="sep"/>');
					} else {
						var menu = $('<a href="javascript:;"><i class="icon fa-fw"></i><span class="label"></span></a>')
							.appendTo($('<li />"').appendTo(ul))
							.on('click', function(e) {
								ul.hide();
								var this$ = $(e.currentTarget);
								setTimeout(function() {
									// trigger command
									sandbox.cmd(this$.attr('data-cmd'), {
										EVENT: {
											EXTRA: this$.data('value')
										}
									});
								}, 1)
							});
						$('.label', menu).text(v.title);
						menu.attr('data-cmd', v.cmd);
						menu.data('value', v.value);
						var icon = $F.fas(v.icon);
						if (v.type == 'radio') {
							menu.addClass('extra_radio');
							if (v.prop) {
								var value = sandbox.$instance.getValue(v.prop, sandbox.$runtime);
								if (value == v.value)
									icon = 'far fa-check-circle';
							}
						}
						$('.icon', menu).addClass(icon);
					}
				})
				ul.css({
					'fontSize': extra.css('fontSize')
				})
				Helper.drift(ul, this$, function() {
					ul.remove();
				}, {
					'boxShadow': 'none'
				})
			});
		},

		pageTitle : function(e) {
			return this.$instance.title(e);
		},

		/**
		 * 打开pdf文件进行预览
		 * @param options {object} 入参json对象
		 *  [options.container] {string} 打开嵌套浏览的控件对象或者id
		 *  [options.file] {string} 需要打开的文件管理路径(或者key--当使用oss存储时，上传文件方法返回的不再是文件名，而是一个key）
		 *  [options.origin] {string} 源文件名。该值为空时，默认为pdf文件。否则按照源文件的类型进行pdf文件的转换
		 * @example
		 * 		// 在div控件c1中打开/test/demo.pdf
		 * 		pdfViewer({
		 * 			container: 'c1',
		 * 			file: '/test/demo.pdf'
		 * 		})
		 *		// 在div控件c1中打开/test/demo.docx
		 *		pdfViewer({
		 *			container: 'c1',
		 *			file: '/test/demo.docx',
		 *			origin: '.docx'
		 *		})
		 */
		pdfViewer : function(options) {
			if (! options.container)
				return;
			if ($.type(options.container) == 'string')
				options.container = this.ctl(options.container);
			this.cabinet('pdfViewer', 'init', options);
		},

		sysDo1 : function(sqlKey, params) {
			var result = {code: -1};
			$F.dataService('SystemDo1', {
				data: $F.sqlbean(sqlKey, this.$token, params),
				async: false,
				success: function(data) {
					result = data;
				}
			})
			return result;
		},

		sysDo2 : function(sqlKey, params, maxRow, page) {
			var result = {code: -1};
			$F.dataService('SystemDo2', {
				data: $F.sqlbean(sqlKey, this.$token, params, maxRow, page),
				async: false,
				success: function(data) {
					if (data.code > 0) {
						result = {
							code: data.code,
							value: {
								columns: data.value[0].columns,
								fields: data.value[0].fields
							}
						}
					} else {
						result = {
							code: -1,
							message: data.message
						}
					}
				}
			})
			return result;
		},

		sysDo3 : function(sqlKey, params, idField, pIdField) {
			var result = {code: -1};
			$F.dataService('SystemDo3', {
				data: $F.sqlbean(sqlKey, this.$token, params, idField, pIdField, true),
				async: false,
				success: function(data) {
					if (data.code > 0) {
						result = data.value.tree;
					} else {
						result = {
							code: -1,
							message: data.message
						}
					}
				}
			})
			return result;
		},

		/**
		 * 注册一个websocket监听器
		 * @param options {object}
		 * 	options.callback {function} 监听回调的方法
		 * 	[options.rid] {string} 监听的凭据
		 * 	[options.ev] {any} 监听回调入参
		 * 	[options.persistence {boolean} 是否持久化，非持久化的监听器在触发一次后将被移除
		 * 	[options.verifyRid] {boolean} 是否校验rid监听凭据 避免重复注册 默认为false
		 */
		wsHook : function(options) {
			window.$E.ws.framework.hook(options.rid, options.callback, options.ev, options.persistence, options.verifyRid);
		},

		/**
		 * 发送一个websocket消息
		 * @param options {object}
		 * 	[options.module] {string} 指定报文的类型
		 * 	[options.data] {any} 报文主体
		 * 	[options.callback] {function} 发送完成后的独立监听回调方法
		 * 	[options.ev] {any} 回调方法的入参
		 */
		wsSend : function(options) {
			window.$E.ws.framework.send(options)
		},

		popover: function(target, layer, options) {
			if ($.type(target) == 'string')
				target = ctl(target);
			if ($.type(layer) == 'string')
				layer = ctl(layer);
			options.onOpen = function(dom) {
				layer.state(0);
			}
			Helper.popover(target.dom, layer.dom, options);
		},

		param: function(key) {
			return this.$project.getParam(key);
		},

		refreshDbMasker() {
			return $F.ajaxSync('JSGenerateServlet2', {
				token : this.$token,
				dbPool : '',
				name : 'FormatFieldsRefresh'
			}, this.$digest);
		},
		/**
		 * 将dom 转换成图片
		 * @param ctlId 要转换成图片的dom元素的控件ID
		 * @param pngName 导出的文件名
		 */
		domToImage (ctlId, pngName) {
			const ctl = this.getCtl(ctlId)
			const element = ctl.dom[0]
			H5Utils.domToImage(element, pngName)
		}
	};

	/**
	 * 执行一个命令组
	 * cmd : {
	 * 		type: {integer},	命令类型
	 * 		exp: {string},		命令执行条件
	 * 		content: {object | string}, 命令主体
	 * 		bind: [{
	 * 			t: {string}, 返回值绑定变量名
	 * 			i: {integer} 返回次序
	 * 		},...],
	 * 		desc: {string} 命令描述
	 * }
	 */
	function doCmdList(sandbox, cmdId, cmdList, globalCmdId) {
		var logHead, filterFlag, start = Date.now();
		try {
			$F.logGroup('Cmd2' + (globalCmdId?'GC':'') + ' - CmdIn', cmdId + '[' + cmdList.length + ']', cmdList);
			//获取导出的excelSql
			if (sandbox.R.EVENT && sandbox.R.EVENT._data_export ) {
				let excelSqls = cmdList.map(item =>{
					if(item.content.sub === 0 && item.content.dbexport === 1){
						return item.content.sql
					}
				})
				sandbox.$ExportSqls = excelSqls.filter(item=>!!item)
			}
			$.each(cmdList, function(i, cmd) {
				logHead = 'Cmd2 - ' + cmdId + '-' + (i + 1);
				if (cmd.exp) {
					//$F.log(logHead, 'Filter: ', cmd.exp);
					if (cmd.exp.trim() == '#P')
						$F.log(logHead, 'Exp: #P');
					else
						filterFlag = doScript(sandbox, cmd.exp, logHead, 'Exp: ')
					if (! filterFlag)
						return;
				}
				var result = undefined;
				switch (cmd.type) {
					case 900:
						// 执行普通脚本
						//$F.log(logHead, 'Content: ', cmd.content);
						result = doScript(sandbox, cmd.content, logHead, 'Content: ');
						break;
					case 800:
						// 执行赋值
						$F.log(logHead, '赋值', '...');
						result = doMutator(sandbox, cmd.content, logHead);
						break;
					case 901:
						$F.log(logHead, 'GetId: ', cmd.content);
						result = doIncrement(sandbox, cmd.content, logHead);
						break;
					case 902:
						// 数据库执行
						var dbpool = cmd.content.dbpool || '__default__', dbtypes = sandbox.$project.dbtype = sandbox.$project.dbtype || {};
						if (dbtypes[dbpool] === undefined) {
							// 获取当前项目的数据库类型
							dbtypes[dbpool] = sandbox.$project.getDbType();
						}
						if (cmd.content.type) {
							var type = cmd.content.type.toUpperCase();
							if (type == 'SQLSERVER')
								type = 'SQL';
							if (type !== dbtypes[dbpool].toUpperCase()) {
								// 不同的数据库类型，忽略执行
								$F.log(logHead, 'DBType:' + cmd.content.type.toUpperCase() + ', Expecting:' + dbtypes[dbpool].toUpperCase() + ', Ignore');
								return;
							}
						}
						switch (cmd.content.sub) {
							case 0:
								// 普通SQL
								$F.log(logHead, 'SQLID' + (cmd.content.dbpool ? ('[' + cmd.content.dbpool + ']') : '') + ': ', cmd.content.sql);
								result = doSql(sandbox, cmd.content, logHead, globalCmdId);
								break;
							case 1:
								// 事务执行
								result = doTransaction(sandbox, cmd.content, logHead, globalCmdId);
								break;
							case 2:
								// DSL定义
								$F.log(logHead, 'DSLID: ', cmd.content.sql);
								result = doDsl(sandbox, cmd.content, logHead);
								break;
							case 3:
								// 存储过程
								$F.log(logHead, 'PROCUDURE: ', cmd.content.name);
								result = doProcedure(sandbox, cmd.content, logHead);
								break;
						}
						break;
					case 903:
						// CTI操作
						cmd.content.type = cmd.content.type || 0;
						$F.log(logHead, 'CTI: ', ['拨打','挂断','会议','完成会议','接电话','转接','多步转','转接完成','保持'][cmd.content.type]);
						result = doCTI(sandbox, cmd.content, logHead);
						break;
					case 905:
						// 执行HTTP请求
						result = doHttp(sandbox, cmd.content, logHead);
						break;
					case 906:
						// 执行动态表操作
						$F.log(logHead, 'XDS: ' + (cmd.content.type == 2 ? 'submit' : cmd.content.table));
						result = doXDS(sandbox, cmd.content, logHead);
						break;
					case 999:
						switch(cmd.content.sub) {
							case 0:
								// 执行全局命令
								$F.log(logHead, 'GLOBALCMD: ' + cmd.content.cmdId);
								doGlobalCmd(sandbox, cmd.content, logHead);
								break;
							case 1:
								// 执行服务器命令
								$F.log(logHead, 'SRVCMD: ' + cmd.content.cmdId);
								result = doServerCmd(sandbox, cmd.content, logHead);
								break;
							case 2:
								// 录音下载
								break;
							case 3:
								// 报表运行
								break;
						}
				}
				// 对单行命令执行结果进行后期处理
				if (result === undefined) {
					$F.log(logHead, 'Done');
				} else {
					if (result == 'STOP-RUN-NEXT-COMMAND') {
						$F.log(logHead, 'Done: STOP-RUN-NEXT-COMMAND');
						return false;
					} else
						setBindProps(sandbox, result, cmd, logHead)
				}
			})

			$F.logGroupEnd('Cmd2' + (globalCmdId?'GC':'') + ' - CmdOut', cmdId + ' [耗时：' + (Date.now() - start) + '\'ms]');
		} catch (e) {
			$F.err('Cmd2 - doCmdList', e)
		}
	}

	/**
	 * 执行脚本
	 */
	function doScript(sandbox, script, logHead, preLog) {
		// 执行脚本前先进行OGNL替换
		var replace = false,
			expression = $F.ognl(script, function(propertyName) {
				replace = true;
				return getVar(sandbox, propertyName);
			}, true);
		if (replace)
			$F.log(logHead, preLog, expression);
		else
			$F.log(logHead, preLog, script);

		return sandbox.evalCmd(i18nResource(expression, sandbox.$runtime).replace(/\beval\((.*)\)/gi, '$1'));
	}

	/**
	 * 执行赋值
	 */
	function doMutator(sandbox, script, logHead) {
		// 执行脚本前先进行OGNL替换
		var replace = false,
			expression = $F.ognl(script, function(propertyName) {
				replace = true;
				return getVar(sandbox, propertyName);
			}, true);
		if (replace && $E.debug)
			console.log(logHead, 'Replace: ', expression);
		return expression;
	}

	/**
	 * 获取自增ID命令
	 * cmd.content = {
	 * 		server: {string}, 	服务器名
	 * 		oid: {string},		原ID
	 * 		key: {string},		主键
	 * 		display: {string},	显示键
	 * 		need: {integer},	是否显示显示键
	 * 		start: {integer},	递增开始值
	 * 		reset: {integer},	是否根据显示键不同重新开始值
	 * 		indent: {integer},	递增长度
	 * }
	 */
	function doIncrement(sandbox, content, logHead) {
		var oid = sandbox.ognl(content.oid);
		if (oid)
			return oid + '';

		var id,
			server = sandbox.ognl(content.server),
			dataRequest = {
				token : sandbox.$token,
				dbPool : '',
				name : 'System.GetID',
				P1 : sandbox.ognl(content.key),
				P2 : sandbox.ognl(content.display),
				P3 : (content.need ? '0':'1'),
				P4 : content.start + '',
				P5 : (content.reset ? '1':'0'),
				P6 : content.indent + ''
			};

		// console.log(dataRequest)
		if (! server) {
			$F.dataService('JSGenerateServlet2', {
				data: dataRequest,
				async: false,
				digest: sandbox.$digest,
				success: function(data) {
					if (data.code > 0)
						id = data.value;
					else
						$F.err('Cmd2 - doIncrement', data.message);
				}
			})
		} else {
			var content = JSON.stringify(dataRequest);
			Ajax.post({
				url: $E.url + '/help/increment_?url=' + encodeURIComponent(server),
				data: content,
				async: false,
				headers: {
					SIGNER: md5(sandbox.$token.substring(sandbox.$digest) + content)
				},
				success: function(data) {
					if (data.code > 0)
						id = data.value;
					else
						$F.err('Cmd2 - doIncrement', '自增ID获取失败');
				}
			})
		}
		return id;
	}

	/**
	 * 执行一个SQL条件片段
	 */
	function doDsl(sandbox, sqlObj, logHead) {
		var DSL = {
			pId: sandbox.$dynGuid,
			sqlId: sqlObj.sql,
			params: {}
		}
		$.each(sqlObj.binds, function(i, v) {
			DSL.params['C' + (i+1)] = getVar(sandbox, v);
		})

		var dslKey = (sqlObj.key || 'default').toUpperCase();
		sandbox.$DSL[dslKey] = sandbox.$DSL[dslKey] || {};
		sandbox.$DSL[dslKey][sqlObj.num] = sandbox.$DSL[dslKey][sqlObj.num] || [];
		sandbox.$DSL[dslKey][sqlObj.num].push(DSL);
		if (Object.keys(DSL.params).length > 0)
			$F.log(logHead, JSON.stringify(DSL.params));
	}

	/**
	 * 执行一条普通SQL
	 */
	function doSql(sandbox, sqlObj, logHead, globalCmdId) {
		var params = {}, DSLS;
		$.each(sqlObj.binds, function(i, v) {
			params['C' + (i+1)] = getVar(sandbox, v);
		})

		if (sqlObj.key)
			DSLS = sandbox.$DSL[sqlObj.key.toUpperCase()];
		if (sqlObj.cmdId) {
			// 异步回调模式
			if (sqlObj.exe) {
				// execute
				$F.dataService('Dyn2CahceDo1', {
					data: {
						token: sandbox.$token,
						dbPool: sandbox.ognl(sqlObj.dbpool),
						dynId: globalCmdId || sandbox.$dynGuid,
						version: globalCmdId ? '' : (sandbox.$struct.publishVersion || ''),
						sqls: [{
							sqlId: sqlObj.sql,
							params: params,
							dsls: DSLS
						}]
					},
					digest: sandbox.$digest,
					silent: true,
					success: function(data) {
						if (data.code < 0)
							$F.err('Cmd2 - doSql', result.message);
						else {
							sandbox.R.RESULT = data.code;
							sandbox.cmd(sqlObj.cmdId)
						}
					}
				})
			} else {
				let pageSize = sqlObj.maxrow == -1? -99 : (sqlObj.maxrow || 0);
				if (sandbox.R.EVENT && sandbox.R.EVENT.PAGESIZE) {
					pageSize = sandbox.R.EVENT.PAGESIZE  || pageSize;
				}
				const data = {
					token: sandbox.$token,
					dbPool: sandbox.ognl(sqlObj.dbpool),
					dynId: globalCmdId || sandbox.$dynGuid,
					version: globalCmdId ? '' : (sandbox.$struct.publishVersion || ''),
					dslFlag: 0,
					sqls: [{
						sqlId: sqlObj.sql,
						params: params,
						dsls: DSLS,
						maxRow: pageSize,
						page: (sandbox.R.EVENT ? (sandbox.R.EVENT.PAGEINDEX || 1) : 1)
					}]
				}
				//如果有导出命令的话，将opts.data返回
				if (sandbox.R.EVENT && sandbox.R.EVENT._data_export ) {
					if((sandbox.$ExportSqls.length && sandbox.$ExportSqls.includes(sqlObj.sql)) ||!sandbox.$ExportSqls.length) {
						sandbox.R.EVENT.callback && sandbox.R.EVENT.callback(data)
					}
				}
				$F.dataService('Dyn2CahceDo2', {
					data: data,
					digest: sandbox.$digest,
					silent: true,
					success: function(data) {
						if (data.code < 0)
							$F.err('Cmd2 - doSql', result.message);
						else {
							if (data.value[0].count == -1)
								$F.err('Cmd2 - doSql', 'SQL执行失败: ' + sqlObj.sql);
							else {
								// console.log(sandbox.R)
								if (sandbox.R.EVENT && sandbox.R.EVENT.PAGEPROP) {
									if(sandbox.R.EVENT.PAGEINFO) {
										setVar(sandbox, sandbox.R.EVENT.PAGEPROP.toLowerCase(), {
											COUNT: data.value[0].count,
											PAGES: data.value[0].pages,
											INDEX: 1
										});
									} else {
										setVar(sandbox, sandbox.R.EVENT.PAGEPROP.toLowerCase(), {
											COUNT: data.value[0].count,
											PAGES: data.value[0].pages,
											INDEX: (sandbox.R.EVENT.PAGEINDEX || 1)
										});
									}
								}
								if (sqlObj.jo)
									sandbox.R.RESULT = $F.makeJsonArray(data.value[0]);
								else
									sandbox.R.RESULT = data.value[0].fields;
								sandbox.cmd(sqlObj.cmdId)
							}
						}
					}
				})
			}
		} else {
			if (sqlObj.exe) {
				// execute
				var result = $F.ajaxSync('Dyn2CahceDo1', {
					token: sandbox.$token,
					dbPool: sandbox.ognl(sqlObj.dbpool),
					dynId: globalCmdId || sandbox.$dynGuid,
					version: globalCmdId ? '' : (sandbox.$struct.publishVersion || ''),
					sqls: [{
						sqlId: sqlObj.sql,
						params: params,
						dsls: DSLS
					}]
				}, sandbox.$digest)
				if (result.code > -1)
					return result.code;
				else
					$F.err('Cmd2 - doSql', result.message);
			} else {
				let pageSize = sqlObj.maxrow == -1? -99 : (sqlObj.maxrow || 0);
				if (sandbox.R.EVENT && sandbox.R.EVENT.PAGESIZE) {
					pageSize = sandbox.R.EVENT.PAGESIZE  || pageSize;
				}
				// 根据有没有R.EVENT.PAGEINFO来决定是哪种分页模式
				let pageType = (sandbox.R.EVENT && sandbox.R.EVENT.PAGEINFO) ? 1 : 0;
				// query
				const data ={
					token: sandbox.$token,
					dbPool: sandbox.ognl(sqlObj.dbpool),
					dynId: globalCmdId || sandbox.$dynGuid,
					version: globalCmdId ? '' : (sandbox.$struct.publishVersion || ''),
					dslFlag: 0,
					sqls: [{
						sqlId: sqlObj.sql,
						params: params,
						dsls: DSLS,
						maxRow: pageType === 1 ? sandbox.R.EVENT.PAGEINFO.PAGESIZE : pageSize,
						page: pageType === 1 ? sandbox.R.EVENT.PAGEINFO.PAGEINDEX : (sandbox.R.EVENT ? (sandbox.R.EVENT.PAGEINDEX || 1) : 1),
						sp: pageType
					}]
				}
				if (sandbox.R.EVENT && sandbox.R.EVENT._data_export ) {
					if((sandbox.$ExportSqls.length && sandbox.$ExportSqls.includes(sqlObj.sql)) ||!sandbox.$ExportSqls.length) {
						sandbox.R.EVENT.callback && sandbox.R.EVENT.callback(data)
					}
				}
				var result = $F.ajaxSync('Dyn2CahceDo2', data, sandbox.$digest)
				if (result.code > 0) {
					if (result.value[0].count == -1)
						$F.err('Cmd2 - doSql', 'SQL执行失败: ' + sqlObj.sql);
					else {
						// console.log(sandbox.R)
						if (sandbox.R.EVENT && sandbox.R.EVENT.PAGEPROP) {
							setVar(sandbox, sandbox.R.EVENT.PAGEPROP.toLowerCase(), {
								COUNT: result.value[0].count,
								PAGES: result.value[0].pages,
								INDEX: (sandbox.R.EVENT.PAGEINDEX || 1)
							});
						}
						if (sqlObj.jo)
							return $F.makeJsonArray(result.value[0]);
						else
							return result.value[0].fields;
					}
				} else
					$F.err('Cmd2 - doSql', result.message);
			}
		}
	}

	/**
	 * 执行一个SQL事务
	 */
	function doTransaction(sandbox, sqlObj, logHead, globalCmdId) {
		let DSLS = sandbox.$DSL[sqlObj.key], sqls = [], params, filterFlag;
		if (! sqlObj.sqls)
			return -1;

		$.each(sqlObj.sqls, function(i, bean) {
			if (bean.condition) {
				if (bean.condition == '#P')
					$F.log(logHead, 'SQLS Exp: #P');
				else
					filterFlag = doScript(sandbox, bean.condition, logHead, 'SQLS Exp: ')
				if (! filterFlag) {
					$F.log(logHead, 'SQLS Exp: Ignored');
					return;
				}
			}
			params = {};
			$.each(bean.binds, function(i, v) {
				params['C' + (i+1)] = getVar(sandbox, v);
			})
			sqls.push({
				sqlId: bean.sql,
				params: params
			})
		})
		$F.log(logHead, 'TRANSACTION: ', sqls.length);

		let result, dataface = (sqlObj.exe ? 'Dyn2CahceDo1' : 'Dyn2CahceDo2');
		$F.dataService(dataface, {
			data: {
				token: sandbox.$token,
				dbPool: sandbox.ognl(sqlObj.dbpool),
				dynId: globalCmdId || sandbox.$dynGuid,
				version: sandbox.$struct.publishVersion || '',
				dslFlag: 1,
				dsls: DSLS,
				sqls: sqls
			},
			digest: sandbox.$digest,
			async: (sqlObj.cmdId ? true: false),
			success: function(data) {
				if (data.code < 0)
					return $F.err('Cmd2 - doTransaction', data.message);
				else {
					if (sqlObj.cmdId) {
						// for callback
						sandbox.R.RESULT = data;
						sandbox.cmd(sqlObj.cmdId)
					} else {
						if (sqlObj.exe)
							result = data.code;
						else {
							result = [];
							data.value.forEach(function(entry) {
								result.push(entry.fields)
							})
						}
					}
				}
			}
		})
		return result;
	}

	/**
	 * 执行一个存储过程
	 */
	function doProcedure(sandbox, procedureObj, logHead) {
		var result, params = {};
		$.each(procedureObj.params, function(i, v) {
			params[v.key] = sandbox.ognl(v.value);
		})
		$F.dataService('DoProc', {
			data: {
				token : sandbox.$token,
				dbPool : sandbox.ognl(procedureObj.dbpool),
				name : procedureObj.name,
				paras : params
			},
			async: (procedureObj.cmdId ? true : false),
			digest: sandbox.$digest,
			success: function(data){
				// console.log(data);
				if (procedureObj.cmdId) {
					sandbox.R.RESULT = data;
					sandbox.cmd(procedureObj.cmdId);
				} else {
					if (data.code > 0)
						result = data.value;
					else
						$F.err('Cmds - doProcedure', data.message);
				}
			}
		})
		if (result !== undefined)
			return result;
	}

	/**
	 * 操作CTI接口
	 */
	function doCTI(sandbox, ctiObj, logHead) {
		var tn = ctiObj.tn, data = ctiObj.data;
		if (tn)
			tn = sandbox.ognl(tn);
		if (data)
			data = sandbox.ognl(data);

		$PublicFun.$CTI.ctiDo(ctiObj.type, tn, data, ctiObj.cmd, sandbox, ctiObj, function(data, ev) {
			$F.busyHide(ev.sc);
			if (ev.cb) {
				sandbox.R.CTIEVENT = data;
				sandbox.cmd(ev.cb);
			}
		}, logHead);
	}

	/**
	 * 操作http接口
	 */
	function doHttp(sandbox, httpObj, logHead) {
		if (httpObj.type == 'rpc')
			return doRPC(sandbox, httpObj, logHead);

		let result, params = {}, heads = {};
		for (let v in (httpObj.params || {})) {
			params[v] = sandbox.ognl(httpObj.params[v])
		}
		for (let v in (httpObj.heads || {})) {
			heads[v] = sandbox.ognl(httpObj.heads[v])
		}

		let start = Date.now(),
			data = {
				url: sandbox.ognl(httpObj.url),
				token: sandbox.$token,
				heads: JSON.stringify(heads),
				params: JSON.stringify(params),
				payload: sandbox.ognl(httpObj.payload || ''),
				contentType: httpObj.contentType || 'json',
				contentEncoding: httpObj.contentEncoding || 'UTF-8',
				dataEncoding: httpObj.dataEncoding || 'UTF-8',
				roleGuid: httpObj.permissionKey || '',
				dynId:sandbox.$instance.dynGuid,
				pagePower: sandbox.$project.getParam('BPOWER')
			},
			timeout = 0,
			httpdf = sandbox.$project.getParam('HTTPDF');

		if (httpObj.timeout && !isNaN(httpObj.timeout)) {
			timeout = parseInt(httpObj.timeout);
		}
		$F.log(logHead, 'HTTP: ' + data.url + ' timeout: ' + timeout);
		if (timeout)
			data.timeout = timeout;

		let ajax = Ajax.post({
			url: $E.url + '/commons/xhr/' + httpObj.type,
			headers: {
				SIGN: $F.signature(data.url + data.heads + data.params + data.payload, sandbox.$token, sandbox.$digest)
			},
			data: data,
			async: (httpObj.cmdId? true:false),
			timeout: timeout,
			success: function(data) {
				result = data;
				if (result.code > 0) {
					if (httpObj.dataType == 'json') {
						try {
							result.value = JSON.parse(data.value);
						} catch(e) {
							result = {
								code: -1,
								message: 'Parse json error',
								value: data.value
							}
						}
					} else if (httpObj.dataType == 'xml') {
						try {
							result.value = $.parseXML(data.value);
						} catch(e) {
							result = {
								code: -1,
								message: 'Parse xml error',
								value: data.value
							}
						}
					}
				} else	{
					$F.err(result.message)
				}
				const spend = Date.now() - start;
				$F.log(logHead, 'XHR - Successed, spend: ' + spend + '\'ms');

				/**
				 * 系统参数FORLUO与HTTPDF冲突，保留HTTPDF参数，FORLUO参数废弃
				 */
				if (httpObj.cmdId) {
					if (httpdf) {
						sandbox.R.SPEND = spend;
						sandbox.R.RESULT = result;
						sandbox.cmd(httpObj.cmdId);
					} else {
						if (result.code > 0) {
							sandbox.R.SPEND = spend;
							sandbox.R.RESULT = result.value;
							sandbox.cmd(httpObj.cmdId);
						} else {
							$F.err(result.message)
						}
					}
				}
			},
			error: function(jqXHR, textStatus, errorThrown) {
				result = {code:-1, message:errorThrown};
				$F.log('Cmd2 - doHttp', '接口失败：' + errorThrown);
			}
		})
		if (! httpObj.cmdId) {
			if (httpdf)
				return result;
			else {
				if (result.code > 0)
					return result.value;
				else
					$F.log('Cmd2 - doHttp', '接口失败：' + result.message)
			}
		}
	}

	function doRPC(sandbox, rpcObj, logHead) {
		/*
		RPC: {
			method: 'doMyWork',					// 对象提供的方法
			params:[{							// doMyWork的执行入參
				type: 'string',					// 入參1: 类型（字符串）
				content: 'test',				// 入參1: 内容
			}, {
				type: 'boolean',				// 入參2: 类型（布林）
				content: true					// 入參2: 内容
			}]
		}
		*/
		var payload = eval('let x = function(){return ' + sandbox.ognl(rpcObj.payload || '') + '}; x();');
		$F.log('Cmd2 - doRPC', 'Script', payload);

		var start = Date.now(), url = sandbox.ognl(rpcObj.url), result, heads = {};
		$.each(Object.keys(rpcObj.heads || {}), function(i, v) {
			heads[v] = sandbox.ognl(rpcObj.heads[v])
		})

		Ajax.post({
			url: $E.url + '/commons/rpc',
			data: {
				url: url,
				token: sandbox.$token,
				heads: JSON.stringify(heads),
				payload: JSON.stringify(payload)
			},
			async: (rpcObj.cmdId? true:false),
			success: function(data) {
				if (data.code > 0) {
					result = data;
					var spend = Date.now() - start;
					$F.log('RPC -> ' + url, spend + '\'ms');
					if (rpcObj.cmdId) {
						sandbox.R.SPEND = spend;
						sandbox.R.RESULT = data.value;
						sandbox.cmd(rpcObj.cmdId);
					}
				} else {
					result = data;
					$F.err('Cmds - doRPC', 'RPC接口操作失败：' + data.message);
				}
			},
			error: function(jqXHR, textStatus, errorThrown) {
				result = {code:-1, message:errorThrown};
				$F.err('Cmds - doRPC', 'RPC接口操作失败：' + errorThrown);
			}
		})
		return result;
	}

	function doXDS(sandbox, xdsObj, logHead) {
		if (xdsObj.type == 2) {
			// 提交缓存
			return sandbox.Bound.submit(xdsObj.dbpool);
		} else if (xdsObj.type == 1) {
			// 提交交叉表
			return doXDSCross(sandbox, xdsObj, logHead);
		}

		if (!xdsObj.table || !xdsObj.prop || !xdsObj.cells)
			return -1;

		var boundData = new BoundData(xdsObj.table),
			keyFlag = 0,
			pkeys = [],
			fkeys = [],
			keyArray = [],
			must = [];
		$.each(xdsObj.cells, function(i, v){
			if (v.pkey)
				pkeys.push(v);
			else if (v.fkey)
				fkeys.push(v);
			if (v.notnull)
				must.push(v.name);
			/*
			 *  F_String : 0,
				F_Float : 1,
				F_Date : 2,
				F_Boolean : 3,
				F_Clob : 4,
				F_Long : 5,
				F_Int : 6
			 */
			if (v.type == 'N')
				v.type = 1;
			else if (v.type == 'D')
				v.type = 2;
			else if (v.type == 'I')
				v.type = 6;
			else
				v.type = 0;
		})
		if (pkeys.length <= 0) {
			// 无主键
			boundData.deleteFlag = 1;
			$.each(fkeys, function(i, v) {
				keyArray.push(v)
			})
		} else {
			boundData.notOperateRec = 1;
			$.each(pkeys, function(i, v) {
				keyArray.push(v)
			})
			keyFlag = 1;
		}
		var property = sandbox.$instance.getProperty(xdsObj.prop, sandbox.$runtime);
		if (property == undefined) {
			$F.err("doXDS", logHead + ' 变量不存在: ' + xdsObj.prop);
			return -1;
		} else {
			if (property.t == 4) {
				var value = getVar(sandbox, xdsObj.prop), valid = true;
				// console.log(value)
				$.each(value, function(i, data) {
					if (keyFlag && !data.flag && !xdsObj.full)	// 有主键模式下，未修改的记录不必保存。无主键模式下，表会被清空，所有记录都必须保存
						return;

					var tmpBound = new BoundData(xdsObj.table);
					$.each(keyArray, function() {
						tmpBound.setKey(this.name);
					})

					$.each(xdsObj.cells, function() {
						if (this.col)
							tmpBound.setNewValue(this.name, (data[this.col-1] == undefined ? '' : data[this.col-1]), this.type);
						else {
							var x = $F.ognl(this.x, function(propertyName) {
								return getVar(sandbox, propertyName);
							});
							tmpBound.setNewValue(this.name, x, this.type);
						}
					})
					$.each(must, function(){
						if (tmpBound.getValue(this) === '') {
							$F.err('Cmds - doXDS', '动态表操作中第' + (i + 1) + '行字段[' + this + ']为必须项，实际值为空')
							valid = false;
							return false;
						}
					})
					if (valid)
						boundData.addSub('F' + i, tmpBound);
					else
						return false;
				})
				if (! valid)
					return -1;
				if (keyFlag && value.removed) {
					// 删除记录仅支持有主键 模式
					$.each(value.removed, function(i, data) {
						var tmpBound = new BoundData(xdsObj.table);
						tmpBound.deleteFlag = 1;
						$.each(keyArray, function() {
							tmpBound.setKey(this.name);
							tmpBound.setNewValue(this.name, (data[this.col-1] == undefined ? '' : data[this.col-1]), 0)
						})
						boundData.addSub('D' + i, tmpBound);
					})
				}

				// BoundData操作成功后，需要清理数组标记
				$.each(value, function(i, data) {
					delete data.flag;
				})
				delete value.removed;

				if (xdsObj.transaction) {
					// 计入缓存，暂时不进行提交
					sandbox.Bound.push(boundData);
					return 1;
				} else {
					// 直接提交数据库
					var result = $F.ajaxSync("SystemDo5", {
						token : sandbox.$token,
						dbPool : xdsObj.dbpool || '',
						xml : boundData.getData()
					}, sandbox.$digest);
					return result.code;
				}
			} else {
				$F.log("doXDS", logHead + ' 变量类型不合法: ' + xdsObj.prop);
				return -1;
			}
		}
	}

	function doXDSCross(sandbox, xdsObj, logHead) {

	}

	/**
	 * 运行全局命令
	 */
	function doGlobalCmd(sandbox, obj, logHead) {
		var cmdId = obj.cmdId;
		let globalCmd = sandbox.$project.globalcmds[cmdId];
		if (!globalCmd) {
			globalCmd = Object.values(sandbox.$project.globalcmds).find(cmd => {
				return cmd.name === cmdId;
			})
		}
		if (!globalCmd) {
			$F.dataService('SystemDo2', {
				data: {
					token: sandbox.$token,
					dbPool: ($E.dataService.saas ? (sandbox.$project.params['CFGDBP'] || '') : ''),
					sqls:[{
						key: 'VE.cmd.content.get',
						params: {
							C1: obj.cmdId
						}
					}]
				},
				async: false,
				digest: sandbox.$digest,
				success: function(data) {
					if (data.code > 0 && data.value[0].count > 0) {
						cmdId = data.value[0].fields[0][2];
						globalCmd = sandbox.$project.globalcmds[cmdId] = {
							cmdId: data.value[0].fields[0][2],
							name: data.value[0].fields[0][1],
							items: JSON.parse(data.value[0].fields[0][0] || '[]')
						}
					}
				}
			})
		}
		if (! globalCmd) {
			$F.log("doGlobalCmd", logHead + ' 获取全局命令失败: ' + cmdId);
			return;
		}

		doCmdList(sandbox, 'G-' + globalCmd.name, globalCmd.items, globalCmd.cmdId);
	}

	/**
	 * 运行服务器命令
	 */
	function doServerCmd(sandbox, obj, logHead) {
		var cmd = getServerCmd(sandbox, obj.cmdId);
		if (! cmd)
			return;

		// 抽取入参
		var params = {
			ins: {},
			outs: cmd.outs
		};
		$.each(cmd.ins, function(i, prop) {
			params.ins[prop.name] = getVar(sandbox, prop.name);
		})
		return doServerCmdImpl(sandbox, obj.cmdId, params, obj.callback);
	}

	function doServerCmdImpl(sandbox, cmdId, params, callback) {
		var result;
		$F.dataService('JSGenerateServlet2', {
			data: {
				token: sandbox.$token,
				dbPool: ($E.dataService.saas ? (sandbox.$project.params['CFGDBP'] || '') : ''),
				name: 'Dyn2.ServerCmd.Run',
				digest: sandbox.$digest,
				data: JSON.stringify({
					token: sandbox.$token,
					digest: sandbox.$digest,
					dbPool: ($E.dataService.saas ? (sandbox.$project.params['CFGDBP'] || '') : ''),
					id: cmdId,
					threadId: Date.now(),
					params: params,
					ds: $E.dataService.key
				})
			},
			async: (callback ? true : false),
			digest: sandbox.$digest,
			success: function(data) {
				if (data.code > 0) {
//					console.log(data)
					if (callback) {
						sandbox.R.RESULT = data.value;
						sandbox.cmd(callback)
					} else
						result = data;
				} else {
					$F.err('Cmd2 - doServerCmd', data.message);
				}
			}
		})
		return result;
	}

	/**
	 * 读取服务器命令的出入参
	 */
	function getServerCmd(sandbox, cmdId) {
		if (! sandbox.$project.srvcmds[cmdId]) {
			$F.dataService('SystemDo2', {
				data: {
					token: sandbox.$token,
					dbPool: ($E.dataService.saas ? (sandbox.$project.params['CFGDBP'] || '') : ''),
					sqls:[{
						key: 'VE.sc.io.get',
						params: {
							C1: cmdId
						}
					}]
				},
				async: false,
				digest: sandbox.$digest,
				success: function(data) {
					if (data.code > 0) {
						if (data.value[0].count < 1) {
							$F.err('Cmd2 - getServerCmd', '读取服务器命令IO失败：' + cmdId);
						} else {
							if (data.value[0].count > 0) {
								sandbox.$project.srvcmds[cmdId] = {
									name: data.value[0].fields[0][2],
									ins: JSON.parse(data.value[0].fields[0][0] || '[]'),
									outs: JSON.parse(data.value[0].fields[0][1] || '[]')
								}
							} else {
								sandbox.$project.srvcmds[cmdId] = {
									name: 'Unknown',
									ins: [],
									outs: []
								}
							}
						}
					} else {
						$F.err('Cmd2 - getServerCmd', data.message);
					}
				}
			})
		}
		return sandbox.$project.srvcmds[cmdId];
	}

	/**
	 * 具有返回值的命令执行结果，对绑定的变量进行赋值
	 */
	function setBindProps(sandbox, result, cmd, logHead) {
		var real, cs;
		$.each(cmd.bind, function(i, bind) {
			if (bind.i > 0) {
				// 对返回值数组进行取行
				if ($.type(result) == 'array') {
					if (result.length > 1) {
						real = result[bind.i - 1];		// 针对普通数组，第i列
					} else {
						if (result.length == 1) {
							if ($.type(result[0]) == 'array') {
								real = result[0][bind.i - 1];	// 针对SystemDo2的二维数组结果，第1行第i列
							} else {
								real = result[0];	// 针对已经转型为object的对象，不需要在递进取值
							}
						} else {
							real = '';
						}
					}
				} else if ($.type(result) == 'string') {
					real = result[bind.i - 1];		// 针对普通字符串，第i字节
				} else {
					$F.err('非法的赋值列，返回结果无下标', result);
					real = result;
				}
			} else {
				if (bind.i !== -1 && $.type(result) == 'array' && result.length == 1 && $.type(result[0]) == 'array' && result[0].length == 1)
					real = result[0][0];
				else
					real = result;
			}

			if ($.type(real) == 'array' && cmd.bind.length > 1)
				setVar(sandbox, bind.t, $.extend(true, [], real));
			else if ($.type(real) == 'object' && cmd.bind.length > 1)
				setVar(sandbox, bind.t, $.extend(true, {}, real));
			else
				setVar(sandbox, bind.t, real);
			$F.log(logHead, 'Done: ' + bind.t + ' => ', real);
		})
		if (!cmd.bind.length)
			$F.log(logHead, 'Done');
	}

	function setVar(sandbox, propertyName, value, quite) {
		if (propertyName.indexOf('.') > -1) {
			// 普通变量
			const property = sandbox.$instance.getProperty(propertyName, sandbox.$runtime);
			if (property) {
				const cs = propertyName.split('.'),
					realValue = sandbox.$instance.getDefaultValue(property.t, value);
				// console.log('realValue: ' + value + ', type=' + $.type(realValue));
				if (property.ns !== 'var' && property.field !== 'tabc') {
					// 公共变量需要进行customer和objective双向绑定
					sandbox.$instance.syncSystemObject(propertyName, realValue);
				}

				if (!property.dirty || property.t == 4 || property.t == 6 || realValue !== sandbox.R[cs[0]][cs[1]]) {
					property.dirty = true;
					sandbox.R[cs[0]][cs[1]] = realValue;

					if (sandbox._completed) {
						// 命令组已经执行完毕，需要自行回调刷新UI
						if ($.isFunction(sandbox.$executer.callback)) {
							var $bindProps = {};
							$bindProps[propertyName] = {
								type : 0,
								data : realValue
							}
							sandbox.$executer.callback($bindProps, sandbox.$runtime);
						}
					} else {
						// 普通变量需要进行值改变消息触发
						sandbox.$bindProps[propertyName] = {
							type : 0,
							data : realValue
						}
					}
				}
			} else {
				if (! quite)
					$F.err('Cmd2 - setVar', '未定义变量: ' + propertyName);
			}
		} else {
			let EXTPROP = propertyName.toUpperCase();
			if (EXTPROP.startWith("OBJ_")) {
				EXTPROP = EXTPROP.substring(4);
				sandbox.$objective.setValue(EXTPROP, value);
			} else if (EXTPROP.startWith("CUST_")) {
				EXTPROP = EXTPROP.substring(5);
				sandbox.$instance.customer.setValue(EXTPROP, value);
			} else
				sandbox.R[propertyName] = value;
		}
	}

	function getVar(sandbox, propertyName) {
		if (propertyName.indexOf('.') > -1) {
			// 普通变量
			return sandbox.$instance.getValue(propertyName.toLowerCase(), sandbox.$runtime);
		} else {
			var cs = propertyName.toUpperCase().split('-'), obj = sandbox.R;
			$.each(cs, function(i, v) {
				if (obj[v] !== undefined) {
					obj = obj[v];
				} else {
					// 全局静态变量
					if (cs.length == 1)
						obj = getStaticVar(sandbox, v);
					else
						obj = '';
					return false;
				}
			})
			return obj;
		}
	}

	function getStaticVar(sandbox, propertyName) {
		propertyName = propertyName.toUpperCase();
		var runtime = sandbox.$runtime,
			cwInfo = runtime.project.cwInfo,
			instance = runtime.instance,
			project = runtime.project,
			struct = runtime.struct;

		try {
			switch(propertyName) {
			case 'C_GUID':
				return Guid.raw();
			case 'C_NOW':
				return sandbox.now();
			case 'DYNGUID':
				return sandbox.$dynGuid;
			case 'DYNID':
				return sandbox.$dynId;
			case 'EPID':
			case 'C_EPID':
				return $E.staff.epid;
			case 'EPIDTB':
			case 'C_EPIDTB':
				return $E.staff.epidSql;
			case 'STAFFID':
			case 'C_BY':
				return $E.staff.id;
			case 'STAFFNAME':
				return $E.staff.name;
			case 'AGENTID':
				return $E.staff.agentId;
			case 'ROLEGROUPID':
			case 'C_GRP':
				return project.groupId;
			case 'ROLEGROUPNAME':
				return project.groupName;
			case 'ANI':
			case 'DNIS':
			case 'EXTENSION':
				if (cwInfo && cwInfo.callInfo && cwInfo.callInfo.getValue) {
					return cwInfo.callInfo.getValue(propertyName.toLowerCase()) || '';
				} else
					return '';
			case 'CALLTYPE':
				if (cwInfo)
					return cwInfo.callType || '';
				else
					return '';
			case 'CUREVENTID':
				if (cwInfo)
					return cwInfo.eventGuid || '';
				else
					return '';
			case 'G_ISSUPER':
				var isSupervisor = parseInt(project.getParam('ISSUPE', 2));
				return ($E.staff.supervisor > isSupervisor) ? 1 : 0;// 大于系统参数值才算是管理员，小于等于都不是
			case 'G_SFASHA':
				return parseInt(project.getParam('SFASHA', 0));
			default:
				if (propertyName.startWith("OBJ_")) {
					propertyName = propertyName.substring(4);
					var propertyValue = sandbox.$objective.getValue(propertyName);
					if (propertyValue === null || propertyValue === undefined) {
						return '';
					}
					return propertyValue;
				} else if (propertyName.startWith("CUST_")) {
					propertyName = propertyName.substring(5);
					var propertyValue = instance.customer.getValue(propertyName)
					if (propertyValue === null || propertyValue === undefined) {
						return '';
					}
					return propertyValue;
				}
			}
		} catch (e) {
			$F.err("Cmd - getStaticVar", "Get [" + propertyName + "] failed", e);
		}
	}

	function displayResult(head, log, value) {
		if (value == undefined)
			$F.log(head, log);
		else if (typeof (value) == 'string')
			$F.log(head, log + ' ← ' + (value == '' ? '\'\'' : value));
		else if (typeof (value) == 'boolean')
			$F.log(head, log + ' ← ' + value);
		else if (typeof (value) == 'number')
			$F.log(head, log + ' ← ' + value);
		else if ($.type(value) == 'array')
			$F.log(head, log + ' ← Array[' + value.length + ']', {
				'Array' : value
			});
		else
			$F.log(head, log + ' ← ', value);
	}

	function i18nResource(value, runtime) {
		if (! value)
			return '';

		let resource = runtime.struct.i18n[$E.lang];
    	var matches = value.match(/\{\{([\u4e00-\u9fa5 | \w | \. | @ | %]+)\}\}/gi), ex, v;
    	if (matches) {
    		for(var i = 0; i<matches.length; i++ ) {
    			value = value.replace(matches[i], function(){
					ex = (/\{\{([\u4e00-\u9fa5 | \w | \. | @ | %]+)\}\}/gi).exec(matches[i]);
					if (resource && resource.hasOwnProperty(ex[1]))
						return resource[ex[1]];
					else
						return ex[1];
						//return matches[i];
				})
    		}
    	}
    	return value;
	}



})(jQuery)
